diff --git a/.gitignore b/.gitignore index a9b3bbbf6b3b438618dde7521bad9df9332b325e..5b7bab79ecb2bcc6f63a413f6d506eb510c06c59 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,315 @@ docs/build .pydevproject snf-astakos-app/astakos/version.py snf-astakos-app/distribute-0.6.10-py2.6.egg -snf-astakos-app/distribute-0.6.10.tar.gz \ No newline at end of file +snf-astakos-app/distribute-0.6.10.tar.gz +snf-astakos-app/build/lib/astakos/__init__.py +snf-astakos-app/build/lib/astakos/im/__init__.py +snf-astakos-app/build/lib/astakos/im/activation_backends.py +snf-astakos-app/build/lib/astakos/im/api/__init__.py +snf-astakos-app/build/lib/astakos/im/api/admin.py +snf-astakos-app/build/lib/astakos/im/api/backends/__init__.py +snf-astakos-app/build/lib/astakos/im/api/backends/base.py +snf-astakos-app/build/lib/astakos/im/api/backends/errors.py +snf-astakos-app/build/lib/astakos/im/api/backends/lib/__init__.py +snf-astakos-app/build/lib/astakos/im/api/backends/lib/django/__init__.py +snf-astakos-app/build/lib/astakos/im/api/backends/notifications.py +snf-astakos-app/build/lib/astakos/im/api/callpoint.py +snf-astakos-app/build/lib/astakos/im/api/client.py +snf-astakos-app/build/lib/astakos/im/api/faults.py +snf-astakos-app/build/lib/astakos/im/api/service.py +snf-astakos-app/build/lib/astakos/im/api/spec.py +snf-astakos-app/build/lib/astakos/im/auth_backends.py +snf-astakos-app/build/lib/astakos/im/context_processors.py +snf-astakos-app/build/lib/astakos/im/cookie.py +snf-astakos-app/build/lib/astakos/im/endpoints/__init__.py +snf-astakos-app/build/lib/astakos/im/endpoints/aquarium/__init__.py +snf-astakos-app/build/lib/astakos/im/endpoints/aquarium/client.py +snf-astakos-app/build/lib/astakos/im/endpoints/aquarium/consumer.py +snf-astakos-app/build/lib/astakos/im/endpoints/aquarium/producer.py +snf-astakos-app/build/lib/astakos/im/endpoints/qh.py +snf-astakos-app/build/lib/astakos/im/forms.py +snf-astakos-app/build/lib/astakos/im/functions.py +snf-astakos-app/build/lib/astakos/im/management/__init__.py +snf-astakos-app/build/lib/astakos/im/management/commands/__init__.py +snf-astakos-app/build/lib/astakos/im/management/commands/_common.py +snf-astakos-app/build/lib/astakos/im/management/commands/full-cleanup.py +snf-astakos-app/build/lib/astakos/im/management/commands/group-add.py +snf-astakos-app/build/lib/astakos/im/management/commands/group-list.py +snf-astakos-app/build/lib/astakos/im/management/commands/group-permissions-add.py +snf-astakos-app/build/lib/astakos/im/management/commands/group-permissions-remove.py~HEAD +snf-astakos-app/build/lib/astakos/im/management/commands/group-permissions-remove.py~future +snf-astakos-app/build/lib/astakos/im/management/commands/group-update.py +snf-astakos-app/build/lib/astakos/im/management/commands/invitation-details.py +snf-astakos-app/build/lib/astakos/im/management/commands/invitation-list.py +snf-astakos-app/build/lib/astakos/im/management/commands/quotaholder-sync.py +snf-astakos-app/build/lib/astakos/im/management/commands/resource-add.py +snf-astakos-app/build/lib/astakos/im/management/commands/resource-list.py +snf-astakos-app/build/lib/astakos/im/management/commands/resource-remove.py +snf-astakos-app/build/lib/astakos/im/management/commands/service-add.py +snf-astakos-app/build/lib/astakos/im/management/commands/service-list.py +snf-astakos-app/build/lib/astakos/im/management/commands/service-remove.py +snf-astakos-app/build/lib/astakos/im/management/commands/service-token-renew.py +snf-astakos-app/build/lib/astakos/im/management/commands/term-add.py +snf-astakos-app/build/lib/astakos/im/management/commands/user-activation-send.py +snf-astakos-app/build/lib/astakos/im/management/commands/user-add.py +snf-astakos-app/build/lib/astakos/im/management/commands/user-details.py +snf-astakos-app/build/lib/astakos/im/management/commands/user-invite.py +snf-astakos-app/build/lib/astakos/im/management/commands/user-list.py +snf-astakos-app/build/lib/astakos/im/management/commands/user-update.py +snf-astakos-app/build/lib/astakos/im/messages.py +snf-astakos-app/build/lib/astakos/im/middleware.py +snf-astakos-app/build/lib/astakos/im/migrations/0001_initial.py +snf-astakos-app/build/lib/astakos/im/migrations/0002_auto__add_field_astakosuser_third_party_identifier.py +snf-astakos-app/build/lib/astakos/im/migrations/0003_auto__add_unique_invitation_username.py +snf-astakos-app/build/lib/astakos/im/migrations/0004_auto__add_field_astakosuser_email_verified.py +snf-astakos-app/build/lib/astakos/im/migrations/0005_auto__add_field_astakosuser_has_credits.py +snf-astakos-app/build/lib/astakos/im/migrations/0006_auto__add_approvalterms__add_field_astakosuser_has_signed_terms__add_f.py +snf-astakos-app/build/lib/astakos/im/migrations/0007_auto__chg_field_astakosuser_email_verified__chg_field_astakosuser_has_.py +snf-astakos-app/build/lib/astakos/im/migrations/0007_auto__del_field_invitation_accepted__del_field_invitation_is_accepted.py +snf-astakos-app/build/lib/astakos/im/migrations/0008_auto__add_emailchange.py +snf-astakos-app/build/lib/astakos/im/migrations/0009_auto__add_service.py +snf-astakos-app/build/lib/astakos/im/migrations/0010_auto__add_field_astakosuser_activation_sent__chg_field_service_url.py +snf-astakos-app/build/lib/astakos/im/migrations/0011_set_old_activation_sent.py +snf-astakos-app/build/lib/astakos/im/migrations/0012_auto__add_additionalmail.py +snf-astakos-app/build/lib/astakos/im/migrations/0013_auto__del_unique_additionalmail_email.py +snf-astakos-app/build/lib/astakos/im/migrations/0014_auto__add_unique_astakosuser_third_party_identifier_provider.py +snf-astakos-app/build/lib/astakos/im/migrations/0015_auto__add_groupkind__add_astakosgroup__add_resourcemetadata__add_astak.py +snf-astakos-app/build/lib/astakos/im/migrations/0015_auto__add_pendingthirdpartyuser__add_unique_pendingthirdpartyuser_prov.py +snf-astakos-app/build/lib/astakos/im/migrations/0016_auto__add_sessioncatalog.py +snf-astakos-app/build/lib/astakos/im/migrations/0017_populate_resource_data.py +snf-astakos-app/build/lib/astakos/im/migrations/0018_auto__add_field_astakosgroup_homepage.py +snf-astakos-app/build/lib/astakos/im/migrations/0020_auto__chg_field_astakosgroup_homepage.py +snf-astakos-app/build/lib/astakos/im/migrations/0021_auto__add_field_astakosgroupquota_uplimit__add_field_astakosuserquota_.py +snf-astakos-app/build/lib/astakos/im/migrations/0022_copy_limit_to_uplimit.py +snf-astakos-app/build/lib/astakos/im/migrations/0024_auto__chg_field_astakosgroupquota_lim.py +snf-astakos-app/build/lib/astakos/im/migrations/0025_case_insensitive_emails.py +snf-astakos-app/build/lib/astakos/im/migrations/0026_auto__add_field_resource_desc__add_field_resource_unit.py +snf-astakos-app/build/lib/astakos/im/migrations/0027_auto__add_field_resource_group.py +snf-astakos-app/build/lib/astakos/im/migrations/0028_auto__add_field_astakosuser_disturbed_quota.py +snf-astakos-app/build/lib/astakos/im/migrations/0029_auto__add_field_astakosgroup_max_participants.py +snf-astakos-app/build/lib/astakos/im/migrations/0030_populate_resource_data.py +snf-astakos-app/build/lib/astakos/im/migrations/0031_populate_group_data.py +snf-astakos-app/build/lib/astakos/im/migrations/__init__.py +snf-astakos-app/build/lib/astakos/im/models.py +snf-astakos-app/build/lib/astakos/im/settings.py +snf-astakos-app/build/lib/astakos/im/static/im/cloudbar/README.rst +snf-astakos-app/build/lib/astakos/im/static/im/cloudbar/cloudbar.css +snf-astakos-app/build/lib/astakos/im/static/im/cloudbar/cloudbar.js +snf-astakos-app/build/lib/astakos/im/static/im/cloudbar/cloudbar.less +snf-astakos-app/build/lib/astakos/im/static/im/cloudbar/home-icon.png +snf-astakos-app/build/lib/astakos/im/static/im/cloudbar/service_cloud.css +snf-astakos-app/build/lib/astakos/im/static/im/cloudbar/service_cyclades.css +snf-astakos-app/build/lib/astakos/im/static/im/cloudbar/service_pithos.css +snf-astakos-app/build/lib/astakos/im/static/im/css/boxsizing.htc +snf-astakos-app/build/lib/astakos/im/static/im/css/browser-fixes.css +snf-astakos-app/build/lib/astakos/im/static/im/css/colorbox.css +snf-astakos-app/build/lib/astakos/im/static/im/css/custom.css +snf-astakos-app/build/lib/astakos/im/static/im/css/dropkick.css +snf-astakos-app/build/lib/astakos/im/static/im/css/formating.css +snf-astakos-app/build/lib/astakos/im/static/im/css/forms.css +snf-astakos-app/build/lib/astakos/im/static/im/css/global.css +snf-astakos-app/build/lib/astakos/im/static/im/css/ie7.css +snf-astakos-app/build/lib/astakos/im/static/im/css/images/ui-bg_flat_0_aaaaaa_40x100.png +snf-astakos-app/build/lib/astakos/im/static/im/css/images/ui-bg_glass_55_fbf9ee_1x400.png +snf-astakos-app/build/lib/astakos/im/static/im/css/images/ui-bg_glass_65_ffffff_1x400.png +snf-astakos-app/build/lib/astakos/im/static/im/css/images/ui-bg_glass_75_dadada_1x400.png +snf-astakos-app/build/lib/astakos/im/static/im/css/images/ui-bg_glass_75_e6e6e6_1x400.png +snf-astakos-app/build/lib/astakos/im/static/im/css/images/ui-bg_glass_75_ffffff_1x400.png +snf-astakos-app/build/lib/astakos/im/static/im/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png +snf-astakos-app/build/lib/astakos/im/static/im/css/images/ui-bg_inset-soft_95_fef1ec_1x100.png +snf-astakos-app/build/lib/astakos/im/static/im/css/images/ui-icons_222222_256x240.png +snf-astakos-app/build/lib/astakos/im/static/im/css/images/ui-icons_2e83ff_256x240.png +snf-astakos-app/build/lib/astakos/im/static/im/css/images/ui-icons_454545_256x240.png +snf-astakos-app/build/lib/astakos/im/static/im/css/images/ui-icons_888888_256x240.png +snf-astakos-app/build/lib/astakos/im/static/im/css/images/ui-icons_cd0a0a_256x240.png +snf-astakos-app/build/lib/astakos/im/static/im/css/jquery-ui-1.8.21.custom.css +snf-astakos-app/build/lib/astakos/im/static/im/css/max480.css +snf-astakos-app/build/lib/astakos/im/static/im/css/max768.css +snf-astakos-app/build/lib/astakos/im/static/im/css/max960.css +snf-astakos-app/build/lib/astakos/im/static/im/css/modules.css +snf-astakos-app/build/lib/astakos/im/static/im/css/print.css +snf-astakos-app/build/lib/astakos/im/static/im/css/uniform.default.css +snf-astakos-app/build/lib/astakos/im/static/im/images/_double-dots.jpg +snf-astakos-app/build/lib/astakos/im/static/im/images/accounts-logo.png +snf-astakos-app/build/lib/astakos/im/static/im/images/arrow-down.png +snf-astakos-app/build/lib/astakos/im/static/im/images/arrow-down_black.png +snf-astakos-app/build/lib/astakos/im/static/im/images/arrow-down_blue.png +snf-astakos-app/build/lib/astakos/im/static/im/images/arrow-down_grey.png +snf-astakos-app/build/lib/astakos/im/static/im/images/arrow-lt.png +snf-astakos-app/build/lib/astakos/im/static/im/images/arrow-up.png +snf-astakos-app/build/lib/astakos/im/static/im/images/arrow-up_blue.png +snf-astakos-app/build/lib/astakos/im/static/im/images/arrow-white-reverse.png +snf-astakos-app/build/lib/astakos/im/static/im/images/arrow-white.png +snf-astakos-app/build/lib/astakos/im/static/im/images/arrow_01.jpg +snf-astakos-app/build/lib/astakos/im/static/im/images/bandwidth-stats.png +snf-astakos-app/build/lib/astakos/im/static/im/images/banner.png +snf-astakos-app/build/lib/astakos/im/static/im/images/black-line.jpg +snf-astakos-app/build/lib/astakos/im/static/im/images/checkbox.png +snf-astakos-app/build/lib/astakos/im/static/im/images/cpu-stats.png +snf-astakos-app/build/lib/astakos/im/static/im/images/create-compute.png +snf-astakos-app/build/lib/astakos/im/static/im/images/create-network.png +snf-astakos-app/build/lib/astakos/im/static/im/images/create-resource-cpu.png +snf-astakos-app/build/lib/astakos/im/static/im/images/create-resource-disk.png +snf-astakos-app/build/lib/astakos/im/static/im/images/create-resource-network.png +snf-astakos-app/build/lib/astakos/im/static/im/images/create-resource-ram.png +snf-astakos-app/build/lib/astakos/im/static/im/images/create-resource-storage.png +snf-astakos-app/build/lib/astakos/im/static/im/images/create-resource-vm.png +snf-astakos-app/build/lib/astakos/im/static/im/images/create-storage.png +snf-astakos-app/build/lib/astakos/im/static/im/images/create.png +snf-astakos-app/build/lib/astakos/im/static/im/images/dashed_border.png +snf-astakos-app/build/lib/astakos/im/static/im/images/disk-stats.png +snf-astakos-app/build/lib/astakos/im/static/im/images/dots.jpg +snf-astakos-app/build/lib/astakos/im/static/im/images/double-dots-unequal.jpg +snf-astakos-app/build/lib/astakos/im/static/im/images/join.png +snf-astakos-app/build/lib/astakos/im/static/im/images/local.png +snf-astakos-app/build/lib/astakos/im/static/im/images/network-stats.png +snf-astakos-app/build/lib/astakos/im/static/im/images/openid.png +snf-astakos-app/build/lib/astakos/im/static/im/images/pictures/accounts_3.png +snf-astakos-app/build/lib/astakos/im/static/im/images/pictures/login_pic.png +snf-astakos-app/build/lib/astakos/im/static/im/images/pictures/ring_bell_beige.gif +snf-astakos-app/build/lib/astakos/im/static/im/images/pictures/signup_pic.png +snf-astakos-app/build/lib/astakos/im/static/im/images/plus-minus-hover.png +snf-astakos-app/build/lib/astakos/im/static/im/images/plus-minus.png +snf-astakos-app/build/lib/astakos/im/static/im/images/porta.png +snf-astakos-app/build/lib/astakos/im/static/im/images/question-mark.jpg +snf-astakos-app/build/lib/astakos/im/static/im/images/quota-related-bg.png +snf-astakos-app/build/lib/astakos/im/static/im/images/ram-stats.png +snf-astakos-app/build/lib/astakos/im/static/im/images/red-stats-vm.png +snf-astakos-app/build/lib/astakos/im/static/im/images/refresh_g.png +snf-astakos-app/build/lib/astakos/im/static/im/images/refresh_lg.png +snf-astakos-app/build/lib/astakos/im/static/im/images/service_okeanos_logo.png +snf-astakos-app/build/lib/astakos/im/static/im/images/service_pithos_logo.png +snf-astakos-app/build/lib/astakos/im/static/im/images/shibboleth.png +snf-astakos-app/build/lib/astakos/im/static/im/images/sound_g.png +snf-astakos-app/build/lib/astakos/im/static/im/images/sound_lg.png +snf-astakos-app/build/lib/astakos/im/static/im/images/sprite.png +snf-astakos-app/build/lib/astakos/im/static/im/images/statistics_icons.png +snf-astakos-app/build/lib/astakos/im/static/im/images/stats-line.jpg +snf-astakos-app/build/lib/astakos/im/static/im/images/storage-stats.png +snf-astakos-app/build/lib/astakos/im/static/im/images/symbols.png +snf-astakos-app/build/lib/astakos/im/static/im/images/symbols2.png +snf-astakos-app/build/lib/astakos/im/static/im/images/twitter.png +snf-astakos-app/build/lib/astakos/im/static/im/images/vm-stats.png +snf-astakos-app/build/lib/astakos/im/static/im/js/common.js +snf-astakos-app/build/lib/astakos/im/static/im/js/forms.js +snf-astakos-app/build/lib/astakos/im/static/im/js/html5.js +snf-astakos-app/build/lib/astakos/im/static/im/js/html5shiv-printshiv.js +snf-astakos-app/build/lib/astakos/im/static/im/js/jqModal.js +snf-astakos-app/build/lib/astakos/im/static/im/js/jquery-1.7.1.min.js +snf-astakos-app/build/lib/astakos/im/static/im/js/jquery-ui-1.8.21.custom.min.js +snf-astakos-app/build/lib/astakos/im/static/im/js/jquery.alerts.js +snf-astakos-app/build/lib/astakos/im/static/im/js/jquery.colorbox.js +snf-astakos-app/build/lib/astakos/im/static/im/js/jquery.dropkick-1.0.0.js +snf-astakos-app/build/lib/astakos/im/static/im/js/jquery.infieldlabel.js +snf-astakos-app/build/lib/astakos/im/static/im/js/jquery.js +snf-astakos-app/build/lib/astakos/im/static/im/js/jquery.labelify.js +snf-astakos-app/build/lib/astakos/im/static/im/js/jquery.tablesorter.js +snf-astakos-app/build/lib/astakos/im/static/im/js/jquery.uniform.js +snf-astakos-app/build/lib/astakos/im/static/im/js/main.js +snf-astakos-app/build/lib/astakos/im/static/im/js/modernizr-2.0.6.js +snf-astakos-app/build/lib/astakos/im/static/im/js/os.js +snf-astakos-app/build/lib/astakos/im/static/im/js/quotas.js +snf-astakos-app/build/lib/astakos/im/static/im/js/resources_list.js +snf-astakos-app/build/lib/astakos/im/static/im/js/underscore.js +snf-astakos-app/build/lib/astakos/im/static/im/less/bootstrap.less +snf-astakos-app/build/lib/astakos/im/static/im/less/django_forms.less +snf-astakos-app/build/lib/astakos/im/static/im/less/forms.less +snf-astakos-app/build/lib/astakos/im/static/im/less/mixins.less +snf-astakos-app/build/lib/astakos/im/static/im/less/patterns.less +snf-astakos-app/build/lib/astakos/im/static/im/less/reset.less +snf-astakos-app/build/lib/astakos/im/static/im/less/scaffolding.less +snf-astakos-app/build/lib/astakos/im/static/im/less/tables.less +snf-astakos-app/build/lib/astakos/im/static/im/less/type.less +snf-astakos-app/build/lib/astakos/im/static/im/less/variables.less +snf-astakos-app/build/lib/astakos/im/static/im/less/xtra.less +snf-astakos-app/build/lib/astakos/im/synnefo_settings.py +snf-astakos-app/build/lib/astakos/im/target/__init__.py +snf-astakos-app/build/lib/astakos/im/target/local.py +snf-astakos-app/build/lib/astakos/im/target/redirect.py +snf-astakos-app/build/lib/astakos/im/target/shibboleth.py +snf-astakos-app/build/lib/astakos/im/tasks.py +snf-astakos-app/build/lib/astakos/im/templates/im/account_base.html +snf-astakos-app/build/lib/astakos/im/templates/im/account_notification.txt +snf-astakos-app/build/lib/astakos/im/templates/im/activation_email.txt +snf-astakos-app/build/lib/astakos/im/templates/im/approval_terms.html +snf-astakos-app/build/lib/astakos/im/templates/im/astakosgroup_create_list.html +snf-astakos-app/build/lib/astakos/im/templates/im/astakosgroup_detail.html +snf-astakos-app/build/lib/astakos/im/templates/im/astakosgroup_form.html +snf-astakos-app/build/lib/astakos/im/templates/im/astakosgroup_form_summary.html +snf-astakos-app/build/lib/astakos/im/templates/im/astakosgroup_list.html +snf-astakos-app/build/lib/astakos/im/templates/im/astakosuserquota_list.html +snf-astakos-app/build/lib/astakos/im/templates/im/base.html +snf-astakos-app/build/lib/astakos/im/templates/im/base_two_cols.html +snf-astakos-app/build/lib/astakos/im/templates/im/billing.html +snf-astakos-app/build/lib/astakos/im/templates/im/captcha.html +snf-astakos-app/build/lib/astakos/im/templates/im/email.txt +snf-astakos-app/build/lib/astakos/im/templates/im/feedback.html +snf-astakos-app/build/lib/astakos/im/templates/im/feedback_mail.txt +snf-astakos-app/build/lib/astakos/im/templates/im/footer.html +snf-astakos-app/build/lib/astakos/im/templates/im/form_render.html +snf-astakos-app/build/lib/astakos/im/templates/im/group_creation_notification.txt +snf-astakos-app/build/lib/astakos/im/templates/im/helpdesk_notification.txt +snf-astakos-app/build/lib/astakos/im/templates/im/invitation.txt +snf-astakos-app/build/lib/astakos/im/templates/im/invitations.html +snf-astakos-app/build/lib/astakos/im/templates/im/login.html +snf-astakos-app/build/lib/astakos/im/templates/im/login_base.html +snf-astakos-app/build/lib/astakos/im/templates/im/one_col_base.html +snf-astakos-app/build/lib/astakos/im/templates/im/profile.html +snf-astakos-app/build/lib/astakos/im/templates/im/projects/list_types.html +snf-astakos-app/build/lib/astakos/im/templates/im/register.html +snf-astakos-app/build/lib/astakos/im/templates/im/resource_list.html +snf-astakos-app/build/lib/astakos/im/templates/im/services_description.html +snf-astakos-app/build/lib/astakos/im/templates/im/signup.html +snf-astakos-app/build/lib/astakos/im/templates/im/signup_complete.html +snf-astakos-app/build/lib/astakos/im/templates/im/switch_accounts_email.txt +snf-astakos-app/build/lib/astakos/im/templates/im/third_party_check_local.html +snf-astakos-app/build/lib/astakos/im/templates/im/third_party_registration.html +snf-astakos-app/build/lib/astakos/im/templates/im/timeline.html +snf-astakos-app/build/lib/astakos/im/templates/im/welcome_email.txt +snf-astakos-app/build/lib/astakos/im/templates/registration/email_change_confirm.html +snf-astakos-app/build/lib/astakos/im/templates/registration/email_change_done.html +snf-astakos-app/build/lib/astakos/im/templates/registration/email_change_email.txt +snf-astakos-app/build/lib/astakos/im/templates/registration/email_change_form.html +snf-astakos-app/build/lib/astakos/im/templates/registration/logged_out.html +snf-astakos-app/build/lib/astakos/im/templates/registration/password_change_form.html +snf-astakos-app/build/lib/astakos/im/templates/registration/password_email.txt +snf-astakos-app/build/lib/astakos/im/templates/registration/password_reset_complete.html +snf-astakos-app/build/lib/astakos/im/templates/registration/password_reset_confirm.html +snf-astakos-app/build/lib/astakos/im/templates/registration/password_reset_done.html +snf-astakos-app/build/lib/astakos/im/templates/registration/password_reset_form.html +snf-astakos-app/build/lib/astakos/im/templatetags/__init__.py +snf-astakos-app/build/lib/astakos/im/templatetags/astakos_tags.py +snf-astakos-app/build/lib/astakos/im/templatetags/filters.py +snf-astakos-app/build/lib/astakos/im/templatetags/formatters.py +snf-astakos-app/build/lib/astakos/im/urls.py +snf-astakos-app/build/lib/astakos/im/util.py +snf-astakos-app/build/lib/astakos/im/views.py +snf-astakos-app/build/lib/astakos/im/widgets.py +snf-astakos-app/build/lib/astakos/urls.py +snf-astakos-app/build/lib/astakos/version.py +snf-astakos-app/dist/snf_astakos_app-0.7.6_279_gc0b2660-py2.6.egg +snf-astakos-app/build/lib/astakos/im/auth_providers.py +snf-astakos-app/build/lib/astakos/im/management/commands/astakos-qh-sync.py +snf-astakos-app/build/lib/astakos/im/management/commands/group-details.py +snf-astakos-app/build/lib/astakos/im/management/commands/group-permissions-remove.py +snf-astakos-app/build/lib/astakos/im/management/commands/user-remove.py +snf-astakos-app/build/lib/astakos/im/migrations/0016_populate_group_data.py +snf-astakos-app/build/lib/astakos/im/migrations/0031_auto__add_sessioncatalog__add_pendingthirdpartyuser__add_unique_pendin.py +snf-astakos-app/build/lib/astakos/im/migrations/0032_auto__add_astakosuserauthprovider__add_unique_astakosuserauthprovider_.py +snf-astakos-app/build/lib/astakos/im/migrations/0033_multiple_auth_providers.py +snf-astakos-app/build/lib/astakos/im/static/im/images/arrow-down_green.png +snf-astakos-app/build/lib/astakos/im/static/im/images/arrow-down_yellow.png +snf-astakos-app/build/lib/astakos/im/static/im/images/arrow-up_green.png +snf-astakos-app/build/lib/astakos/im/static/im/images/how_it_works.png +snf-astakos-app/build/lib/astakos/im/templates/im/account_creation_notification.txt +snf-astakos-app/build/lib/astakos/im/templates/im/auth/local_login_form.html +snf-astakos-app/build/lib/astakos/im/templates/im/auth/local_login_prompt.html +snf-astakos-app/build/lib/astakos/im/templates/im/auth/local_signup_prompt.html +snf-astakos-app/build/lib/astakos/im/templates/im/auth/shibboleth_login.html +snf-astakos-app/build/lib/astakos/im/templates/im/auth/shibboleth_login_prompt.html +snf-astakos-app/build/lib/astakos/im/templates/im/auth/shibboleth_quick_login.html +snf-astakos-app/build/lib/astakos/im/templates/im/how_it_works.html +snf-astakos-app/build/lib/astakos/im/templates/im/profile_bak.html +snf-astakos-app/build/lib/astakos/im/templates/im/resource_usage.html +snf-astakos-app/build/lib/astakos/im/tests.py +snf-astakos-app/dist/snf_astakos_app-0.7.6_343_g666c749-py2.6.egg \ No newline at end of file diff --git a/snf-astakos-app/astakos/im/api/backends/lib/django/__init__.py b/snf-astakos-app/astakos/im/api/backends/lib/django/__init__.py index 9988b737ffdddca4ce8a427b360fdd13c2dbb303..76b08e2297d7b316a3a074064b1611209d3062d2 100644 --- a/snf-astakos-app/astakos/im/api/backends/lib/django/__init__.py +++ b/snf-astakos-app/astakos/im/api/backends/lib/django/__init__.py @@ -33,19 +33,28 @@ from django.db import IntegrityError, transaction from django.core.exceptions import ObjectDoesNotExist +from django.conf import settings from functools import wraps from smtplib import SMTPException from astakos.im.models import ( - AstakosUser, AstakosGroup, GroupKind, Resource, Service, RESOURCE_SEPARATOR + AstakosUser, AstakosGroup, GroupKind, Resource, Service, RESOURCE_SEPARATOR, + Project, ProjectApplication, ProjectMembership, filter_queryset_by_property ) from astakos.im.api.backends.base import BaseBackend, SuccessResult, FailureResult from astakos.im.api.backends.errors import ( ItemNotExists, ItemExists, MissingIdentifier, MultipleItemsExist ) +# from astakos.im.api.backends.lib.notifications import EmailNotification + from astakos.im.util import reserved_email, model_to_dict -from astakos.im.endpoints.qh import get_quota +from astakos.im.endpoints.qh import get_quota, send_quota +from astakos.im.settings import SITENAME +try: + from astakos.im.messages import astakos_messages +except: + pass import logging @@ -56,7 +65,6 @@ DEFAULT_CONTENT_TYPE = None def safe(func): """Decorator function for views that implement an API method.""" - @transaction.commit_manually @wraps(func) def wrapper(self, *args, **kwargs): logger.debug('%s %s %s' % (func, args, kwargs)) @@ -64,10 +72,8 @@ def safe(func): data = func(self, *args, **kwargs) or () except Exception, e: logger.exception(e) - transaction.rollback() return FailureResult(e) else: - transaction.commit() return SuccessResult(data) return wrapper @@ -107,7 +113,10 @@ class DjangoBackend(BaseBackend): q = model.objects.all() if filter: q = q.filter(id__in=filter) - return map(lambda o: model_to_dict(o, exclude=[]), q) + return map(lambda o: self._details(o), q) + + def _details(self, obj): + return model_to_dict(obj, exclude=[]) def _create_object(self, model, **kwargs): o = model.objects.create(**kwargs) @@ -136,6 +145,7 @@ class DjangoBackend(BaseBackend): permissions = kwargs.pop('permissions', ()) groups = kwargs.pop('groups', ()) password = kwargs.pop('password', None) + provider = kwargs.pop('provider', 'local') u = self._create_object(AstakosUser, **kwargs) @@ -145,10 +155,10 @@ class DjangoBackend(BaseBackend): u.policies = policies u.extended_groups = groups - if not u.has_auth_provider('local'): - u.add_auth_provider('local') + if not u.has_auth_provider(provider): + u.add_auth_provider(provider) - return self._list(AstakosUser, filter=(u.id,)) + return self._details(u) @safe def add_policies(self, user_id, update=False, policies=()): @@ -180,6 +190,7 @@ class DjangoBackend(BaseBackend): except ObjectDoesNotExist, e: append((service, resource, e)) return rejected + @safe def add_permissions(self, user_id, permissions=()): user = self._lookup_user(user_id) @@ -252,7 +263,7 @@ class DjangoBackend(BaseBackend): resources = kwargs.pop('resources', ()) s = self._create_object(Service, **kwargs) s.resources = resources - return self._list(Service, filter=(s.id,)) + return self._details(s) @safe def remove_services(self, ids=()): @@ -308,4 +319,152 @@ class DjangoBackend(BaseBackend): g.policies = policies # g.members = members g.owners = owners - return self._list(AstakosGroup, filter=(g.id,)) + return self._details(g) + + + @safe + def submit_application(self, **kwargs): + app = self._create_object(ProjectApplication, **kwargs) + notification = build_notification( + settings.SERVER_EMAIL, + [settings.ADMINS], + _(GROUP_CREATION_SUBJECT) % {'group':app.definition.name}, + _('An new project application identified by %(serial)s has been submitted.') % app.serial + ) + notification.send() + + @safe + def list_applications(self): + return self._list(ProjectAppication) + + @safe + def approve_application(self, serial): + app = self._lookup_object(ProjectAppication, serial=serial) + notify = False + if not app.precursor_application: + kwargs = { + 'application':app, + 'creation_date':datetime.now(), + 'last_approval_date':datetime.now(), + } + project = self._create_object(Project, **kwargs) + else: + project = app.precursor_application.project + last_approval_date = project.last_approval_date + if project.is_valid: + project.application = app + project.last_approval_date = datetime.now() + project.save() + else: + raise Exception(_(astakos_messages.INVALID_PROJECT) % project.__dict__) + + r = _synchonize_project(project.serial) + if not r.is_success: + # revert to precursor + project.appication = app.precursor_application + if project.application: + project.last_approval_date = last_approval_date + project.save() + r = synchonize_project(project.serial) + if not r.is_success: + raise Exception(_(astakos_messages.QH_SYNC_ERROR)) + else: + project.last_application_synced = app + project.save() + sender, recipients, subject, message + notification = build_notification( + settings.SERVER_EMAIL, + [project.owner.email], + _('Project application has been approved on %s alpha2 testing' % SITENAME), + _('Your application request %(serial)s has been apporved.') + ) + notification.send() + + + @safe + def list_projects(self, filter_property=None): + if filter_property: + q = filter_queryset_by_property( + Project.objects.all(), + filter_property + ) + return map(lambda o: self._details(o), q) + return self._list(Project) + + + + @safe + def add_project_member(self, serial, user_id, request_user): + project = self._lookup_object(Project, serial=serial) + user = self.lookup_user(user_id) + if not project.owner == request_user: + raise Exception(_(astakos_messages.NOT_PROJECT_OWNER)) + + if not project.is_alive: + raise Exception(_(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__) + if len(project.members) + 1 > project.limit_on_members_number: + raise Exception(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED)) + m = self._lookup_object(ProjectMembership, person=user, project=project) + if m.is_accepted: + return + m.is_accepted = True + m.decision_date = datetime.now() + m.save() + notification = build_notification( + settings.SERVER_EMAIL, + [user.email], + _('Your membership on project %(name)s has been accepted.') % project.definition.__dict__, + _('Your membership on project %(name)s has been accepted.') % project.definition.__dict__, + ) + notification.send() + + @safe + def remove_project_member(self, serial, user_id, request_user): + project = self._lookup_object(Project, serial=serial) + if not project.is_alive: + raise Exception(_(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__) + if not project.owner == request_user: + raise Exception(_(astakos_messages.NOT_PROJECT_OWNER)) + user = self.lookup_user(user_id) + m = self._lookup_object(ProjectMembership, person=user, project=project) + if not m.is_accepted: + return + m.is_accepted = False + m.decision_date = datetime.now() + m.save() + notification = build_notification( + settings.SERVER_EMAIL, + [user.email], + _('Your membership on project %(name)s has been removed.') % project.definition.__dict__, + _('Your membership on project %(name)s has been removed.') % project.definition.__dict__, + ) + notification.send() + + @safe + def suspend_project(self, serial): + project = self._lookup_object(Project, serial=serial) + project.suspend() + notification = build_notification( + settings.SERVER_EMAIL, + [project.owner.email], + _('Project %(name)s has been suspended on %s alpha2 testing' % SITENAME), + _('Project %(name)s has been suspended on %s alpha2 testing' % SITENAME) + ) + notification.send() + + @safe + def terminate_project(self, serial): + project = self._lookup_object(Project, serial=serial) + project.termination() + notification = build_notification( + settings.SERVER_EMAIL, + [project.owner.email], + _('Project %(name)s has been terminated on %s alpha2 testing' % SITENAME), + _('Project %(name)s has been terminated on %s alpha2 testing' % SITENAME) + ) + notification.send() + + @safe + def synchonize_project(self, serial): + project = self._lookup_object(Project, serial=serial) + project.sync() \ No newline at end of file diff --git a/snf-astakos-app/astakos/im/fixtures/member_accept_policies b/snf-astakos-app/astakos/im/fixtures/member_accept_policies new file mode 100644 index 0000000000000000000000000000000000000000..80398a36ded79442544720e742f44d4a9a31eb2e --- /dev/null +++ b/snf-astakos-app/astakos/im/fixtures/member_accept_policies @@ -0,0 +1,26 @@ +[ + { + "model": "im.memberacceptpolicy", + "pk": 1, + "fields": { + "policy": "auto_accept", + "description": "new join requests are automatically accepted by the system" + } + }, + { + "model": "im.memberacceptpolicy", + "pk": 2, + "fields": { + "policy": "owner_accepts", + "description": "new join requests must be accepted by the owner of the project" + } + }, + { + "model": "im.memberacceptpolicy", + "pk": 3, + "fields": { + "policy": "closed", + "description": "no new members can join the project, even if old ones leave" + } + } +] diff --git a/snf-astakos-app/astakos/im/forms.py b/snf-astakos-app/astakos/im/forms.py index 8fafc100f0a8c9ec063c4637cf680cc80990f7c4..d3bba9118794ede0fc35e8e63b272e75bc940765 100644 --- a/snf-astakos-app/astakos/im/forms.py +++ b/snf-astakos-app/astakos/im/forms.py @@ -35,9 +35,11 @@ from random import random from django import forms from django.utils.translation import ugettext as _ -from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm, - PasswordResetForm, PasswordChangeForm, - SetPasswordForm) +from django.contrib.auth.forms import ( + UserCreationForm, AuthenticationForm, + PasswordResetForm, PasswordChangeForm, + SetPasswordForm +) from django.core.mail import send_mail from django.contrib.auth.tokens import default_token_generator from django.template import Context, loader @@ -50,10 +52,12 @@ from django.forms.models import fields_for_model from django.db import transaction from django.utils.encoding import smart_unicode from django.core import validators +from django.contrib.auth.models import AnonymousUser from astakos.im.models import ( AstakosUser, EmailChange, AstakosGroup, Invitation, GroupKind, - Resource, PendingThirdPartyUser, get_latest_terms, RESOURCE_SEPARATOR + Resource, PendingThirdPartyUser, get_latest_terms, RESOURCE_SEPARATOR, + ProjectDefinition, ProjectApplication, create_application ) from astakos.im.settings import ( INVITATIONS_PER_LEVEL, BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY, @@ -902,3 +906,104 @@ class ExtendedSetPasswordForm(SetPasswordForm): except BaseException, e: logger.exception(e) return super(ExtendedSetPasswordForm, self).save(commit=commit) + + +class ProjectApplicationForm(forms.ModelForm): + name = forms.CharField( + validators=[validators.RegexValidator( + DOMAIN_VALUE_REGEX, + _(astakos_messages.DOMAIN_VALUE_ERR), + 'invalid' + )], + widget=forms.TextInput(attrs={'placeholder': 'eg. foo.ece.ntua.gr'}), + help_text="Name should be in the form of dns" + ) + comments = forms.CharField(widget=forms.Textarea, required=False) + + class Meta: + model = ProjectDefinition + exclude = ('resource_grants') + + def __init__(self, *args, **kwargs): + #update QueryDict + args = list(args) + qd = args.pop(0).copy() + members_unlimited = qd.pop('members_unlimited', False) + members_uplimit = qd.pop('members_uplimit', None) + + #substitue QueryDict + args.insert(0, qd) + + super(AstakosGroupCreationForm, self).__init__(*args, **kwargs) + + self.fields.keyOrder = ['kind', 'name', 'homepage', 'desc', + 'issue_date', 'expiration_date', + 'moderation_enabled', 'max_participants'] + def add_fields((k, v)): + k = k.partition('_proxy')[0] + self.fields[k] = forms.IntegerField( + required=False, + widget=forms.HiddenInput(), + min_value=1 + ) + map(add_fields, + ((k, v) for k,v in qd.iteritems() if k.endswith('_uplimit')) + ) + + def add_fields((k, v)): + self.fields[k] = forms.BooleanField( + required=False, + #widget=forms.HiddenInput() + ) + map(add_fields, + ((k, v) for k,v in qd.iteritems() if k.startswith('is_selected_')) + ) + + def clean(self): + userid = self.data.get('user', None)[0] + self.user = None + if userid: + try: + self.user = AstakosUser.objects.get(id=userid) + except AstakosUser.DoesNotExist: + pass + if not self.user: + raise forms.ValidationError(_(astakos_messages.NO_APPLICANT)) + super(ProjectApplicationForm, self).clean() + return self.cleaned_data + + def resource_policies(self, clean=True): + if clean: + self.clean() + policies = [] + append = policies.append + for name, uplimit in self.cleaned_data.iteritems(): + subs = name.split('_uplimit') + if len(subs) == 2: + prefix, suffix = subs + s, sep, r = prefix.partition(RESOURCE_SEPARATOR) + resource = Resource.objects.get(service__name=s, name=r) + + # keep only resource limits for selected resource groups + if self.cleaned_data.get( + 'is_selected_%s' % resource.group, False + ): + append(dict(service=s, resource=r, uplimit=uplimit)) + return policies + + def save(self, commit=True): + definition = super(ProjectApplicationForm, self).save(commit=commit) + definition.resource_policies=self.resource_policies(clean=False) + applicant = self.user + comments = self.cleaned_data.pop('comments', None) + try: + precursor_application = self.instance.projectapplication + except: + precursor_application = None + return create_application( + definition, + applicant, + comments, + precursor_application, + commit + ) diff --git a/snf-astakos-app/astakos/im/management/commands/quotaholder-sync.py b/snf-astakos-app/astakos/im/management/commands/astakos-qh-sync.py similarity index 93% rename from snf-astakos-app/astakos/im/management/commands/quotaholder-sync.py rename to snf-astakos-app/astakos/im/management/commands/astakos-qh-sync.py index 307a271cc200d3d21034dc214f29f96f4f2ddd8f..f045eb8c94fb2a2cc047a833cdbde3fcb98beabe 100644 --- a/snf-astakos-app/astakos/im/management/commands/quotaholder-sync.py +++ b/snf-astakos-app/astakos/im/management/commands/astakos-qh-sync.py @@ -31,7 +31,7 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. -from django.core.management.base import BaseCommand, CommandError +from django.core.management.base import NoArgsCommand, CommandError from astakos.im.models import AstakosUser, Resource from astakos.im.endpoints.qh import register_users, register_resources @@ -40,10 +40,10 @@ import logging logger = logging.getLogger(__name__) -class Command(BaseCommand): +class Command(NoArgsCommand): help = "Send user information and resource quota in the Quotaholder" - def handle(self, *args, **options): + def handle_noargs(self, **options): try: register_resources(Resource.objects.all()) register_users(AstakosUser.objects.all()) diff --git a/snf-astakos-app/astakos/im/management/commands/group-details.py b/snf-astakos-app/astakos/im/management/commands/group-details.py index 6e601fc67f4e19a164666ef63814edb1c9194aec..4276615ef8971d7f03074249c632426232a51e0a 100644 --- a/snf-astakos-app/astakos/im/management/commands/group-details.py +++ b/snf-astakos-app/astakos/im/management/commands/group-details.py @@ -39,12 +39,12 @@ from django.db import models from astakos.im.models import AstakosGroup class Command(BaseCommand): - args = "<group name>" + args = "<group id or name>" help = "Show group info" def handle(self, *args, **options): if len(args) != 1: - raise CommandError("Please provide a group name") + raise CommandError("Please provide a group id or name") group = AstakosGroup.objects name_or_id = args[0].decode('utf8') diff --git a/snf-astakos-app/astakos/im/management/commands/invitation-list.py b/snf-astakos-app/astakos/im/management/commands/invitation-list.py index 67b5b61806816bfb51090e9dae941e203e7d1ec9..9112a2fedbc4fec40743a0399f05363915506ed6 100644 --- a/snf-astakos-app/astakos/im/management/commands/invitation-list.py +++ b/snf-astakos-app/astakos/im/management/commands/invitation-list.py @@ -33,17 +33,17 @@ from optparse import make_option -from django.core.management.base import BaseCommand, CommandError +from django.core.management.base import NoArgsCommand, CommandError from astakos.im.models import Invitation from ._common import format_bool -class Command(BaseCommand): +class Command(NoArgsCommand): help = "List invitations" - option_list = BaseCommand.option_list + ( + option_list = NoArgsCommand.option_list + ( make_option('-c', action='store_true', dest='csv', @@ -51,10 +51,7 @@ class Command(BaseCommand): help="Use pipes to separate values"), ) - def handle(self, *args, **options): - if args: - raise CommandError("Command doesn't accept any arguments") - + def handle_noargs(self, **options): invitations = Invitation.objects.all().order_by('id') labels = ('id', 'inviter', 'email', 'real name', 'code', 'consumed') diff --git a/snf-astakos-app/astakos/im/management/commands/resource-list.py b/snf-astakos-app/astakos/im/management/commands/resource-list.py index 93bd7361f8288d69894c7323574182b5c4b2f0a2..0ba679992300e8ed3a5a355870a4e02a8a8e9541 100644 --- a/snf-astakos-app/astakos/im/management/commands/resource-list.py +++ b/snf-astakos-app/astakos/im/management/commands/resource-list.py @@ -33,15 +33,15 @@ from optparse import make_option -from django.core.management.base import BaseCommand +from django.core.management.base import NoArgsCommand from astakos.im.models import Resource -class Command(BaseCommand): +class Command(NoArgsCommand): help = "List resources" - option_list = BaseCommand.option_list + ( + option_list = NoArgsCommand.option_list + ( make_option('-c', action='store_true', dest='csv', @@ -49,7 +49,7 @@ class Command(BaseCommand): help="Use pipes to separate values"), ) - def handle(self, *args, **options): + def handle_noargs(self, **options): resources = Resource.objects.select_related().all() labels = ('id', 'service', 'name') diff --git a/snf-astakos-app/astakos/im/management/commands/user-update.py b/snf-astakos-app/astakos/im/management/commands/user-update.py index d1b6d2351cbb92818602af6861112a15cf855a5e..d1b267bc700709814c75edc40967c2f115ccc1fe 100644 --- a/snf-astakos-app/astakos/im/management/commands/user-update.py +++ b/snf-astakos-app/astakos/im/management/commands/user-update.py @@ -58,10 +58,6 @@ class Command(BaseCommand): dest='password', metavar='PASSWORD', help="Set user's password"), - make_option('--provider', - dest='provider', - metavar='PROVIDER', - help="Set user's provider"), make_option('--renew-token', action='store_true', dest='renew_token', @@ -189,10 +185,6 @@ class Command(BaseCommand): if password is not None: user.set_password(password) - provider = options.get('provider') - if provider is not None: - user.provider = provider - password = None if options['renew_password']: password = AstakosUser.objects.make_random_password() diff --git a/snf-astakos-app/astakos/im/messages.py b/snf-astakos-app/astakos/im/messages.py index 2f85d32b3bd405fe5e4c22fbfeaa7b78e2e46c7c..1c413cff26e90436e9e78178d50d976c4729cb94 100644 --- a/snf-astakos-app/astakos/im/messages.py +++ b/snf-astakos-app/astakos/im/messages.py @@ -113,11 +113,11 @@ MISSING_NEXT_PARAMETER = 'No next parameter' INVITATION_SENT = 'Invitation sent to %(email)s.' VERIFICATION_SENT = 'Verification sent.' SWITCH_ACCOUNT_LINK_SENT = 'This email is already associated with another local account. \ - To change this account to a shibboleth one follow the link in the verification email sent to %(email)s. \ - Otherwise just ignore it.' + To change this account to a shibboleth one follow the link in the verification email sent to %(email)s. \ + Otherwise just ignore it.' NOTIFICATION_SENT = 'Your request for an account was successfully received and is now pending approval. \ - You will be notified by email in the next few days. \ - Thanks for your interest in ~okeanos! The GRNET team.' + You will be notified by email in the next few days. \ + Thanks for your interest in ~okeanos! The GRNET team.' ACTIVATION_SENT = 'Activation sent.' REGISTRATION_COMPLETED = 'Registration completed. You can now login.' @@ -126,4 +126,11 @@ NO_RESPONSE = 'There is no response.' NOT_ALLOWED_NEXT_PARAM = 'Not allowed next parameter.' MISSING_KEY_PARAMETER = 'Missing key parameter.' INVALID_KEY_PARAMETER = 'Invalid key.' -DOMAIN_VALUE_ERR = 'Enter a valid domain.' \ No newline at end of file +DOMAIN_VALUE_ERR = 'Enter a valid domain.' +QH_SYNC_ERROR = 'Failed to get synchronized with quotaholder.' +UNIQUE_PROJECT_NAME_CONSTRAIN_ERR = 'The project name (as specified in its application\'s definition) must be unique among all active projects.' +INVALID_PROJECT = 'Project %(serial)s is invalid.' +NOT_ALIVE_PROJECT = 'Project %(serial)s is not alive.' +NOT_PROJECT_OWNER = 'Only project owner can perform this action.' +MEMBER_NUMBER_LIMIT_REACHED = 'Maximum participant number has been reached.' +NO_APPLICANT = 'Project application requires an applicant. None found.' \ No newline at end of file diff --git a/snf-astakos-app/astakos/im/migrations/0017_populate_resource_data.py b/snf-astakos-app/astakos/im/migrations/0017_populate_resource_data.py index 04b8ded9d54d89c78e1fe77e57d0875ae7a7ee3f..1611ba0fdaa9d438bdfbd9773528a54622c89b4c 100644 --- a/snf-astakos-app/astakos/im/migrations/0017_populate_resource_data.py +++ b/snf-astakos-app/astakos/im/migrations/0017_populate_resource_data.py @@ -9,70 +9,14 @@ import logging logger = logging.getLogger(__name__) class Migration(DataMigration): - + "Obsolete migration." + def forwards(self, orm): - "Write your forwards methods here." - - try: - default = orm.AstakosGroup.objects.get(name='default') - except orm.AstakosGroup.DoesNotExist: - return - - def create_policies(args): - sn, dict = args - url = dict.get('url') - resources = dict.get('resources') or () - s, created = orm.Service.objects.get_or_create( - name=sn, - defaults={'url': url} - ) - - for r in resources: - try: - rn = r.pop('name', '') - uplimit = r.pop('uplimit', None) - r, created = orm.Resource.objects.get_or_create( - service=s, - name=rn, - defaults=r) - except Exception, e: - print "Cannot create resource ", rn - continue - else: - q, created = orm.AstakosGroupQuota.objects.get_or_create( - group=default, - resource=r, - defaults={ - 'uplimit':uplimit, - } - ) - map(create_policies, SERVICES.iteritems()) + pass def backwards(self, orm): - try: - default = orm.AstakosGroup.objects.get(name='default') - except orm.AstakosGroup.DoesNotExist: - return - - def destroy_policies(args): - sn, dict = args - url = dict.get('url') - resources = dict.get('resources') or () - for r in resources: - rn = r.get('name', '') - try: - q = orm.AstakosGroupQuota.objects.get( - group=default, - resource__name=rn) - q.delete() - q = orm.Resource.objects.get(service__name=sn, name=rn) - q.delete() - except Exception, e: - print "Cannot create resource ", rn - continue - - map(destroy_policies, SERVICES.iteritems()) - + pass + models = { 'auth.group': { 'Meta': {'object_name': 'Group'}, diff --git a/snf-astakos-app/astakos/im/migrations/0024_auto__chg_field_astakosgroupquota_lim.py b/snf-astakos-app/astakos/im/migrations/0024_auto__chg_field_astakosgroupquota_lim.py index ff4cba218236d7480041f800e91cdb0302b79d27..3a318e34f10b118612120b8488763403df5fdb70 100644 --- a/snf-astakos-app/astakos/im/migrations/0024_auto__chg_field_astakosgroupquota_lim.py +++ b/snf-astakos-app/astakos/im/migrations/0024_auto__chg_field_astakosgroupquota_lim.py @@ -16,13 +16,7 @@ class Migration(SchemaMigration): def backwards(self, orm): - - # Changing field 'AstakosGroupQuota.limit' - db.alter_column('im_astakosgroupquota', 'limit', self.gf('django.db.models.fields.PositiveIntegerField')(default=None)) - - # Changing field 'AstakosUserQuota.limit' - db.alter_column('im_astakosuserquota', 'limit', self.gf('django.db.models.fields.PositiveIntegerField')(default=None)) - + pass models = { 'auth.group': { diff --git a/snf-astakos-app/astakos/im/migrations/0034_auto__add_projectapplication__add_project__add_projectmembership__add_.py b/snf-astakos-app/astakos/im/migrations/0034_auto__add_projectapplication__add_project__add_projectmembership__add_.py new file mode 100644 index 0000000000000000000000000000000000000000..28e2a60e5d7217ac2b5abf5d00925d56819f1cd8 --- /dev/null +++ b/snf-astakos-app/astakos/im/migrations/0034_auto__add_projectapplication__add_project__add_projectmembership__add_.py @@ -0,0 +1,357 @@ +# 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 'ProjectApplication' + db.create_table('im_projectapplication', ( + ('serial', self.gf('django.db.models.fields.CharField')(default='a3e0784bfd924d4ba9a1181332d5e9', unique=True, max_length=30, primary_key=True)), + ('applicant', self.gf('django.db.models.fields.related.ForeignKey')(related_name='my_project_applications', to=orm['im.AstakosUser'])), + ('owner', self.gf('django.db.models.fields.related.ForeignKey')(related_name='own_project_applications', to=orm['im.AstakosUser'])), + ('comments', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('definition', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['im.ProjectDefinition'], unique=True)), + ('issue_date', self.gf('django.db.models.fields.DateTimeField')()), + ('precursor_application', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['im.ProjectApplication'], unique=True, null=True, blank=True)), + )) + db.send_create_signal('im', ['ProjectApplication']) + + # Adding model 'Project' + db.create_table('im_project', ( + ('serial', self.gf('django.db.models.fields.CharField')(default='ab6c5646977c4e678daff27ff3c7cb', unique=True, max_length=30, primary_key=True)), + ('application', self.gf('django.db.models.fields.related.OneToOneField')(related_name='project', unique=True, to=orm['im.ProjectApplication'])), + ('creation_date', self.gf('django.db.models.fields.DateTimeField')()), + ('last_approval_date', self.gf('django.db.models.fields.DateTimeField')()), + ('termination_date', self.gf('django.db.models.fields.DateTimeField')()), + ('last_synced_application', self.gf('django.db.models.fields.related.OneToOneField')(blank=True, related_name='last_project', unique=True, null=True, to=orm['im.ProjectApplication'])), + )) + db.send_create_signal('im', ['Project']) + + # Adding model 'ProjectMembership' + db.create_table('im_projectmembership', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('person', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['im.AstakosUser'])), + ('project', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['im.Project'])), + ('issue_date', self.gf('django.db.models.fields.DateField')(default=datetime.datetime(2012, 12, 6, 10, 11, 33, 387204))), + ('decision_date', self.gf('django.db.models.fields.DateField')(null=True, db_index=True)), + ('is_accepted', self.gf('django.db.models.fields.BooleanField')(default=False)), + )) + db.send_create_signal('im', ['ProjectMembership']) + + # Adding unique constraint on 'ProjectMembership', fields ['person', 'project'] + db.create_unique('im_projectmembership', ['person_id', 'project_id']) + + # Adding model 'MemberAcceptPolicy' + db.create_table('im_memberacceptpolicy', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('policy', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255, db_index=True)), + ('description', self.gf('django.db.models.fields.CharField')(max_length=80)), + )) + db.send_create_signal('im', ['MemberAcceptPolicy']) + + # Adding model 'ProjectDefinition' + db.create_table('im_projectdefinition', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=80)), + ('homepage', self.gf('django.db.models.fields.URLField')(max_length=255, null=True, blank=True)), + ('description', self.gf('django.db.models.fields.TextField')(null=True)), + ('start_date', self.gf('django.db.models.fields.DateTimeField')()), + ('end_date', self.gf('django.db.models.fields.DateTimeField')()), + ('member_accept_policy', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['im.MemberAcceptPolicy'])), + ('limit_on_members_number', self.gf('django.db.models.fields.PositiveIntegerField')(null=True, blank=True)), + )) + db.send_create_signal('im', ['ProjectDefinition']) + + # Adding model 'ProjectResourceGrant' + db.create_table('im_projectresourcegrant', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('member_limit', self.gf('django.db.models.fields.BigIntegerField')(null=True)), + ('project_limit', self.gf('django.db.models.fields.BigIntegerField')(null=True)), + ('resource', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['im.Resource'])), + ('project_definition', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['im.ProjectDefinition'], blank=True)), + )) + db.send_create_signal('im', ['ProjectResourceGrant']) + + # Adding unique constraint on 'ProjectResourceGrant', fields ['resource', 'project_definition'] + db.create_unique('im_projectresourcegrant', ['resource_id', 'project_definition_id']) + + + def backwards(self, orm): + + # Removing unique constraint on 'ProjectResourceGrant', fields ['resource', 'project_definition'] + db.delete_unique('im_projectresourcegrant', ['resource_id', 'project_definition_id']) + + # Removing unique constraint on 'ProjectMembership', fields ['person', 'project'] + db.delete_unique('im_projectmembership', ['person_id', 'project_id']) + + # Deleting model 'ProjectApplication' + db.delete_table('im_projectapplication') + + # Deleting model 'Project' + db.delete_table('im_project') + + # Deleting model 'ProjectMembership' + db.delete_table('im_projectmembership') + + # Deleting model 'MemberAcceptPolicy' + db.delete_table('im_memberacceptpolicy') + + # Deleting model 'ProjectDefinition' + db.delete_table('im_projectdefinition') + + # Deleting model 'ProjectResourceGrant' + db.delete_table('im_projectresourcegrant') + + + 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'}) + }, + 'im.additionalmail': { + 'Meta': {'object_name': 'AdditionalMail'}, + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}) + }, + 'im.approvalterms': { + 'Meta': {'object_name': 'ApprovalTerms'}, + 'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 12, 6, 10, 11, 33, 376205)', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'im.astakosgroup': { + 'Meta': {'object_name': 'AstakosGroup', '_ormbases': ['auth.Group']}, + 'approval_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 12, 6, 10, 11, 33, 368608)'}), + 'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'estimated_participants': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'expiration_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'group_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.Group']", 'unique': 'True', 'primary_key': 'True'}), + 'homepage': ('django.db.models.fields.URLField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'issue_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'kind': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.GroupKind']"}), + 'max_participants': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'moderation_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'policy': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.AstakosGroupQuota']", 'blank': 'True'}) + }, + 'im.astakosgroupquota': { + 'Meta': {'unique_together': "(('resource', 'group'),)", 'object_name': 'AstakosGroupQuota'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosGroup']", 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'limit': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}), + 'uplimit': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}) + }, + 'im.astakosuser': { + 'Meta': {'unique_together': "(('provider', 'third_party_identifier'),)", 'object_name': 'AstakosUser', '_ormbases': ['auth.User']}, + 'activation_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'astakos_groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.AstakosGroup']", 'symmetrical': 'False', 'through': "orm['im.Membership']", 'blank': 'True'}), + 'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'date_signed_terms': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'disturbed_quota': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'email_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'has_credits': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'has_signed_terms': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'invitations': ('django.db.models.fields.IntegerField', [], {'default': '100'}), + 'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'owner': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'owner'", 'null': 'True', 'to': "orm['im.AstakosGroup']"}), + 'policy': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.AstakosUserQuota']", 'symmetrical': 'False'}), + 'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {}), + 'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'im.astakosuserauthprovider': { + 'Meta': {'unique_together': "(('identifier', 'module', 'user'),)", 'object_name': 'AstakosUserAuthProvider'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'affiliation': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'auth_backend': ('django.db.models.fields.CharField', [], {'default': "'astakos'", 'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'module': ('django.db.models.fields.CharField', [], {'default': "'local'", 'max_length': '255'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_providers'", 'to': "orm['im.AstakosUser']"}) + }, + 'im.astakosuserquota': { + 'Meta': {'unique_together': "(('resource', 'user'),)", 'object_name': 'AstakosUserQuota'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'limit': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}), + 'uplimit': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}) + }, + 'im.emailchange': { + 'Meta': {'object_name': 'EmailChange'}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'requested_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 12, 6, 10, 11, 33, 378081)'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emailchange_user'", 'unique': 'True', 'to': "orm['im.AstakosUser']"}) + }, + 'im.groupkind': { + 'Meta': {'object_name': 'GroupKind'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}) + }, + 'im.invitation': { + 'Meta': {'object_name': 'Invitation'}, + 'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}), + 'consumed': ('django.db.models.fields.DateTimeField', [], {'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'}), + 'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.AstakosUser']"}), + 'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'im.memberacceptpolicy': { + 'Meta': {'object_name': 'MemberAcceptPolicy'}, + 'description': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'policy': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}) + }, + 'im.membership': { + 'Meta': {'unique_together': "(('person', 'group'),)", 'object_name': 'Membership'}, + 'date_joined': ('django.db.models.fields.DateField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'date_requested': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime(2012, 12, 6, 10, 11, 33, 373957)', 'blank': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosGroup']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}) + }, + 'im.pendingthirdpartyuser': { + 'Meta': {'unique_together': "(('provider', 'third_party_identifier'),)", 'object_name': 'PendingThirdPartyUser'}, + 'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'token': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'im.project': { + 'Meta': {'object_name': 'Project'}, + 'application': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'project'", 'unique': 'True', 'to': "orm['im.ProjectApplication']"}), + 'creation_date': ('django.db.models.fields.DateTimeField', [], {}), + 'last_approval_date': ('django.db.models.fields.DateTimeField', [], {}), + 'last_synced_application': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'last_project'", 'unique': 'True', 'null': 'True', 'to': "orm['im.ProjectApplication']"}), + 'members': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.AstakosUser']", 'through': "orm['im.ProjectMembership']", 'symmetrical': 'False'}), + 'serial': ('django.db.models.fields.CharField', [], {'default': "'ab6c5646977c4e678daff27ff3c7cb'", 'unique': 'True', 'max_length': '30', 'primary_key': 'True'}), + 'termination_date': ('django.db.models.fields.DateTimeField', [], {}) + }, + 'im.projectapplication': { + 'Meta': {'object_name': 'ProjectApplication'}, + 'applicant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'my_project_applications'", 'to': "orm['im.AstakosUser']"}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'definition': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['im.ProjectDefinition']", 'unique': 'True'}), + 'issue_date': ('django.db.models.fields.DateTimeField', [], {}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'own_project_applications'", 'to': "orm['im.AstakosUser']"}), + 'precursor_application': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['im.ProjectApplication']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'serial': ('django.db.models.fields.CharField', [], {'default': "'a3e0784bfd924d4ba9a1181332d5e9'", 'unique': 'True', 'max_length': '30', 'primary_key': 'True'}) + }, + 'im.projectdefinition': { + 'Meta': {'object_name': 'ProjectDefinition'}, + 'description': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'end_date': ('django.db.models.fields.DateTimeField', [], {}), + 'homepage': ('django.db.models.fields.URLField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'limit_on_members_number': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'member_accept_policy': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.MemberAcceptPolicy']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'resource_grants': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.ProjectResourceGrant']", 'blank': 'True'}), + 'start_date': ('django.db.models.fields.DateTimeField', [], {}) + }, + 'im.projectmembership': { + 'Meta': {'unique_together': "(('person', 'project'),)", 'object_name': 'ProjectMembership'}, + 'decision_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'issue_date': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime(2012, 12, 6, 10, 11, 33, 387204)'}), + 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Project']"}) + }, + 'im.projectresourcegrant': { + 'Meta': {'unique_together': "(('resource', 'project_definition'),)", 'object_name': 'ProjectResourceGrant'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'member_limit': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}), + 'project_definition': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.ProjectDefinition']", 'blank': 'True'}), + 'project_limit': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}), + 'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}) + }, + 'im.resource': { + 'Meta': {'object_name': 'Resource'}, + 'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'group': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'meta': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.ResourceMetadata']", 'symmetrical': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}), + 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Service']"}), + 'unit': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'im.resourcemetadata': { + 'Meta': {'object_name': 'ResourceMetadata'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}), + 'value': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'im.service': { + 'Meta': {'object_name': 'Service'}, + 'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'icon': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}), + 'url': ('django.db.models.fields.FilePathField', [], {'max_length': '100'}) + }, + 'im.sessioncatalog': { + 'Meta': {'object_name': 'SessionCatalog'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sessions'", 'null': 'True', 'to': "orm['im.AstakosUser']"}) + } + } + + complete_apps = ['im'] diff --git a/snf-astakos-app/astakos/im/migrations/0035_populate_resource_data.py b/snf-astakos-app/astakos/im/migrations/0035_populate_resource_data.py new file mode 100644 index 0000000000000000000000000000000000000000..cf99572e1a39d630bd956a422a0c3bbb47e55a86 --- /dev/null +++ b/snf-astakos-app/astakos/im/migrations/0035_populate_resource_data.py @@ -0,0 +1,321 @@ +# encoding: utf-8 + +from south.v2 import DataMigration + +from astakos.im.settings import SERVICES + +import logging + +logger = logging.getLogger(__name__) + +class Migration(DataMigration): + + def forwards(self, orm): + "Write your forwards methods here." + try: + default = orm.AstakosGroup.objects.get(name='default') + except orm.AstakosGroup.DoesNotExist: + return + + def create_policies(args): + sn, dict = args + url = dict.get('url') + resources = dict.get('resources') or () + s, created = orm.Service.objects.get_or_create( + name=sn, + defaults={'url': url} + ) + + for r in resources: + try: + rn = r.pop('name', '') + uplimit = r.pop('uplimit', None) + r, created = orm.Resource.objects.get_or_create( + service=s, + name=rn, + defaults=r) + except Exception, e: + print "Cannot create resource ", rn + continue + else: + q, created = orm.AstakosGroupQuota.objects.get_or_create( + group=default, + resource=r, + defaults={ + 'uplimit':uplimit, + } + ) + map(create_policies, SERVICES.iteritems()) + + def backwards(self, orm): + try: + default = orm.AstakosGroup.objects.get(name='default') + except orm.AstakosGroup.DoesNotExist: + return + + def destroy_policies(args): + sn, dict = args + url = dict.get('url') + resources = dict.get('resources') or () + for r in resources: + rn = r.get('name', '') + try: + q = orm.AstakosGroupQuota.objects.get( + group=default, + resource__name=rn) + q.delete() + q = orm.Resource.objects.get(service__name=sn, name=rn) + q.delete() + except Exception, e: + print "Cannot create resource ", rn + continue + + map(destroy_policies, SERVICES.iteritems()) + + 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'}) + }, + 'im.additionalmail': { + 'Meta': {'object_name': 'AdditionalMail'}, + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}) + }, + 'im.approvalterms': { + 'Meta': {'object_name': 'ApprovalTerms'}, + 'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 12, 6, 9, 57, 4, 932883)', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'im.astakosgroup': { + 'Meta': {'object_name': 'AstakosGroup', '_ormbases': ['auth.Group']}, + 'approval_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 12, 6, 9, 57, 4, 925144)'}), + 'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'estimated_participants': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'expiration_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'group_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.Group']", 'unique': 'True', 'primary_key': 'True'}), + 'homepage': ('django.db.models.fields.URLField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'issue_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'kind': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.GroupKind']"}), + 'max_participants': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'moderation_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'policy': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.AstakosGroupQuota']", 'blank': 'True'}) + }, + 'im.astakosgroupquota': { + 'Meta': {'unique_together': "(('resource', 'group'),)", 'object_name': 'AstakosGroupQuota'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosGroup']", 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'limit': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}), + 'uplimit': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}) + }, + 'im.astakosuser': { + 'Meta': {'unique_together': "(('provider', 'third_party_identifier'),)", 'object_name': 'AstakosUser', '_ormbases': ['auth.User']}, + 'activation_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'astakos_groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.AstakosGroup']", 'symmetrical': 'False', 'through': "orm['im.Membership']", 'blank': 'True'}), + 'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'date_signed_terms': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'disturbed_quota': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'email_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'has_credits': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'has_signed_terms': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'invitations': ('django.db.models.fields.IntegerField', [], {'default': '100'}), + 'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'owner': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'owner'", 'null': 'True', 'to': "orm['im.AstakosGroup']"}), + 'policy': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.AstakosUserQuota']", 'symmetrical': 'False'}), + 'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {}), + 'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'im.astakosuserauthprovider': { + 'Meta': {'unique_together': "(('identifier', 'module', 'user'),)", 'object_name': 'AstakosUserAuthProvider'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'affiliation': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'auth_backend': ('django.db.models.fields.CharField', [], {'default': "'astakos'", 'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'module': ('django.db.models.fields.CharField', [], {'default': "'local'", 'max_length': '255'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_providers'", 'to': "orm['im.AstakosUser']"}) + }, + 'im.astakosuserquota': { + 'Meta': {'unique_together': "(('resource', 'user'),)", 'object_name': 'AstakosUserQuota'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'limit': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}), + 'uplimit': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}) + }, + 'im.emailchange': { + 'Meta': {'object_name': 'EmailChange'}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'requested_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 12, 6, 9, 57, 4, 934567)'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emailchange_user'", 'unique': 'True', 'to': "orm['im.AstakosUser']"}) + }, + 'im.groupkind': { + 'Meta': {'object_name': 'GroupKind'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}) + }, + 'im.invitation': { + 'Meta': {'object_name': 'Invitation'}, + 'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}), + 'consumed': ('django.db.models.fields.DateTimeField', [], {'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'}), + 'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.AstakosUser']"}), + 'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'im.memberacceptpolicy': { + 'Meta': {'object_name': 'MemberAcceptPolicy'}, + 'description': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'policy': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}) + }, + 'im.membership': { + 'Meta': {'unique_together': "(('person', 'group'),)", 'object_name': 'Membership'}, + 'date_joined': ('django.db.models.fields.DateField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'date_requested': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime(2012, 12, 6, 9, 57, 4, 930535)', 'blank': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosGroup']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}) + }, + 'im.pendingthirdpartyuser': { + 'Meta': {'unique_together': "(('provider', 'third_party_identifier'),)", 'object_name': 'PendingThirdPartyUser'}, + 'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'token': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'im.project': { + 'Meta': {'object_name': 'Project'}, + 'application': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'project'", 'unique': 'True', 'to': "orm['im.ProjectApplication']"}), + 'creation_date': ('django.db.models.fields.DateTimeField', [], {}), + 'last_approval_date': ('django.db.models.fields.DateTimeField', [], {}), + 'last_synced_application': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'last_project'", 'unique': 'True', 'null': 'True', 'to': "orm['im.ProjectApplication']"}), + 'members': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.AstakosUser']", 'through': "orm['im.ProjectMembership']", 'symmetrical': 'False'}), + 'serial': ('django.db.models.fields.CharField', [], {'default': "'3853dff39e254f1cac9f1e76a235df'", 'unique': 'True', 'max_length': '30', 'primary_key': 'True'}), + 'termination_date': ('django.db.models.fields.DateTimeField', [], {}) + }, + 'im.projectapplication': { + 'Meta': {'object_name': 'ProjectApplication'}, + 'applicant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'my_project_applications'", 'to': "orm['im.AstakosUser']"}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'definition': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['im.ProjectDefinition']", 'unique': 'True'}), + 'issue_date': ('django.db.models.fields.DateTimeField', [], {}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'own_project_applications'", 'to': "orm['im.AstakosUser']"}), + 'precursor_application': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['im.ProjectApplication']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'serial': ('django.db.models.fields.CharField', [], {'default': "'84b9d92532ef4258a6980a046f9126'", 'unique': 'True', 'max_length': '30', 'primary_key': 'True'}) + }, + 'im.projectdefinition': { + 'Meta': {'object_name': 'ProjectDefinition'}, + 'description': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'end_date': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'limit_on_members_number': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'member_accept_policy': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.MemberAcceptPolicy']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'resource_grants': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.ProjectResourceGrant']", 'blank': 'True'}), + 'start_date': ('django.db.models.fields.DateTimeField', [], {}) + }, + 'im.projectmembership': { + 'Meta': {'unique_together': "(('person', 'project'),)", 'object_name': 'ProjectMembership'}, + 'decision_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'issue_date': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime(2012, 12, 6, 9, 57, 4, 944049)'}), + 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Project']"}) + }, + 'im.projectresourcegrant': { + 'Meta': {'unique_together': "(('resource', 'project_definition'),)", 'object_name': 'ProjectResourceGrant'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'member_limit': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}), + 'project_definition': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.ProjectDefinition']", 'blank': 'True'}), + 'project_limit': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}), + 'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}) + }, + 'im.resource': { + 'Meta': {'object_name': 'Resource'}, + 'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'group': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'meta': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.ResourceMetadata']", 'symmetrical': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}), + 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Service']"}), + 'unit': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'im.resourcemetadata': { + 'Meta': {'object_name': 'ResourceMetadata'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}), + 'value': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'im.service': { + 'Meta': {'object_name': 'Service'}, + 'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'icon': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}), + 'url': ('django.db.models.fields.FilePathField', [], {'max_length': '100'}) + }, + 'im.sessioncatalog': { + 'Meta': {'object_name': 'SessionCatalog'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sessions'", 'null': 'True', 'to': "orm['im.AstakosUser']"}) + } + } + + complete_apps = ['im'] diff --git a/snf-astakos-app/astakos/im/models.py b/snf-astakos-app/astakos/im/models.py index 3a4748c239cb70695824bf52b88d7971bee494f0..9ed9ef9764dd74d40826063b26e53164df1e80b6 100644 --- a/snf-astakos-app/astakos/im/models.py +++ b/snf-astakos-app/astakos/im/models.py @@ -62,9 +62,11 @@ from django.conf import settings from django.utils.importlib import import_module from django.core.validators import email_re -from astakos.im.settings import (DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL, - AUTH_TOKEN_DURATION, BILLING_FIELDS, - EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL) +from astakos.im.settings import ( + DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL, + AUTH_TOKEN_DURATION, BILLING_FIELDS, + EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL +) from astakos.im.endpoints.qh import ( register_users, send_quota, register_resources ) @@ -73,6 +75,8 @@ from astakos.im.endpoints.aquarium.producer import report_user_event from astakos.im.functions import send_invitation #from astakos.im.tasks import propagate_groupmembers_quota +from astakos.im.notifications import build_notification + import astakos.im.messages as astakos_messages logger = logging.getLogger(__name__) @@ -88,14 +92,14 @@ RESOURCE_SEPARATOR = '.' inf = float('inf') class Service(models.Model): - name = models.CharField('Name', max_length=255, unique=True, db_index=True) + name = models.CharField(_('Name'), max_length=255, unique=True, db_index=True) url = models.FilePathField() icon = models.FilePathField(blank=True) - auth_token = models.CharField('Authentication Token', max_length=32, + auth_token = models.CharField(_('Authentication Token'), max_length=32, null=True, blank=True) - auth_token_created = models.DateTimeField('Token creation date', null=True) + auth_token_created = models.DateTimeField(_('Token creation date'), null=True) auth_token_expires = models.DateTimeField( - 'Token expiration date', null=True) + _('Token expiration date'), null=True) def renew_token(self): md5 = hashlib.md5() @@ -133,24 +137,24 @@ class Service(models.Model): class ResourceMetadata(models.Model): - key = models.CharField('Name', max_length=255, unique=True, db_index=True) - value = models.CharField('Value', max_length=255) + key = models.CharField(_('Name'), max_length=255, unique=True, db_index=True) + value = models.CharField(_('Value'), max_length=255) class Resource(models.Model): - name = models.CharField('Name', max_length=255, unique=True, db_index=True) + name = models.CharField(_('Name'), max_length=255, unique=True, db_index=True) meta = models.ManyToManyField(ResourceMetadata) service = models.ForeignKey(Service) - desc = models.TextField('Description', null=True) - unit = models.CharField('Name', null=True, max_length=255) - group = models.CharField('Group', null=True, max_length=255) + desc = models.TextField(_('Description'), null=True) + unit = models.CharField(_('Name'), null=True, max_length=255) + group = models.CharField(_('Group'), null=True, max_length=255) def __str__(self): return '%s%s%s' % (self.service, RESOURCE_SEPARATOR, self.name) class GroupKind(models.Model): - name = models.CharField('Name', max_length=255, unique=True, db_index=True) + name = models.CharField(_('Name'), max_length=255, unique=True, db_index=True) def __str__(self): return self.name @@ -159,8 +163,8 @@ class GroupKind(models.Model): class AstakosGroup(Group): kind = models.ForeignKey(GroupKind) homepage = models.URLField( - 'Homepage Url', max_length=255, null=True, blank=True) - desc = models.TextField('Description', null=True) + _('Homepage Url'), max_length=255, null=True, blank=True) + desc = models.TextField(_('Description'), null=True) policy = models.ManyToManyField( Resource, null=True, @@ -168,30 +172,31 @@ class AstakosGroup(Group): through='AstakosGroupQuota' ) creation_date = models.DateTimeField( - 'Creation date', + _('Creation date'), default=datetime.now() ) issue_date = models.DateTimeField('Start date', null=True) expiration_date = models.DateTimeField( - 'End date', + _('Expiration date'), + null=True null=True ) moderation_enabled = models.BooleanField( - 'Moderated membership?', + _('Moderated membership?'), default=True ) approval_date = models.DateTimeField( - 'Activation date', + _('Activation date'), null=True, blank=True ) estimated_participants = models.PositiveIntegerField( - 'Estimated #members', + _('Estimated #members'), null=True, blank=True, ) max_participants = models.PositiveIntegerField( - 'Maximum numder of participants', + _('Maximum numder of participants'), null=True, blank=True ) @@ -235,19 +240,9 @@ class AstakosGroup(Group): self.save() quota_disturbed.send(sender=self, users=self.approved_members) - @transaction.commit_manually def approve_member(self, person): m, created = self.membership_set.get_or_create(person=person) - try: - m.approve() - except: - transaction.rollback() - raise - else: - transaction.commit() - -# def disapprove_member(self, person): -# self.membership_set.remove(person=person) + m.approve() @property def members(self): @@ -322,44 +317,44 @@ class AstakosUser(User): """ Extends ``django.contrib.auth.models.User`` by defining additional fields. """ - affiliation = models.CharField('Affiliation', max_length=255, blank=True, + affiliation = models.CharField(_('Affiliation'), max_length=255, blank=True, null=True) # DEPRECATED FIELDS: provider, third_party_identifier moved in # AstakosUserProvider model. - provider = models.CharField('Provider', max_length=255, blank=True, + provider = models.CharField(_('Provider'), max_length=255, blank=True, null=True) # ex. screen_name for twitter, eppn for shibboleth - third_party_identifier = models.CharField('Third-party identifier', + third_party_identifier = models.CharField(_('Third-party identifier'), max_length=255, null=True, blank=True) #for invitations user_level = DEFAULT_USER_LEVEL - level = models.IntegerField('Inviter level', default=user_level) + level = models.IntegerField(_('Inviter level'), default=user_level) invitations = models.IntegerField( - 'Invitations left', default=INVITATIONS_PER_LEVEL.get(user_level, 0)) + _('Invitations left'), default=INVITATIONS_PER_LEVEL.get(user_level, 0)) - auth_token = models.CharField('Authentication Token', max_length=32, + auth_token = models.CharField(_('Authentication Token'), max_length=32, null=True, blank=True) - auth_token_created = models.DateTimeField('Token creation date', null=True) + auth_token_created = models.DateTimeField(_('Token creation date'), null=True) auth_token_expires = models.DateTimeField( - 'Token expiration date', null=True) + _('Token expiration date'), null=True) - updated = models.DateTimeField('Update date') - is_verified = models.BooleanField('Is verified?', default=False) + updated = models.DateTimeField(_('Update date')) + is_verified = models.BooleanField(_('Is verified?'), default=False) - email_verified = models.BooleanField('Email verified?', default=False) + email_verified = models.BooleanField(_('Email verified?'), default=False) - has_credits = models.BooleanField('Has credits?', default=False) + has_credits = models.BooleanField(_('Has credits?'), default=False) has_signed_terms = models.BooleanField( - 'I agree with the terms', default=False) + _('I agree with the terms'), default=False) date_signed_terms = models.DateTimeField( - 'Signed terms date', null=True, blank=True) + _('Signed terms date'), null=True, blank=True) activation_sent = models.DateTimeField( - 'Activation sent data', null=True, blank=True) + _('Activation sent data'), null=True, blank=True) policy = models.ManyToManyField( Resource, null=True, through='AstakosUserQuota') @@ -370,7 +365,7 @@ class AstakosUser(User): through='Membership') __has_signed_terms = False - disturbed_quota = models.BooleanField('Needs quotaholder syncing', + disturbed_quota = models.BooleanField(_('Needs quotaholder syncing'), default=False, db_index=True) objects = AstakosUserManager() @@ -700,16 +695,16 @@ class AstakosUserAuthProvider(models.Model): """ Available user authentication methods. """ - affiliation = models.CharField('Affiliation', max_length=255, blank=True, + affiliation = models.CharField(_('Affiliation'), max_length=255, blank=True, null=True, default=None) user = models.ForeignKey(AstakosUser, related_name='auth_providers') - module = models.CharField('Provider', max_length=255, blank=False, + module = models.CharField(_('Provider'), max_length=255, blank=False, default='local') - identifier = models.CharField('Third-party identifier', + identifier = models.CharField(_('Third-party identifier'), max_length=255, null=True, blank=True) active = models.BooleanField(default=True) - auth_backend = models.CharField('Backend', max_length=255, blank=False, + auth_backend = models.CharField(_('Backend'), max_length=255, blank=False, default='astakos') objects = AstakosUserAuthProviderManager() @@ -779,10 +774,12 @@ class Membership(models.Model): quota_disturbed.send(sender=self, users=(self.person,)) def disapprove(self): + approved = self.is_approved() self.delete() - quota_disturbed.send(sender=self, users=(self.person,)) + if approved: + quota_disturbed.send(sender=self, users=(self.person,)) -class AstakosQuotaManager(models.Manager): +class ExtendedManager(models.Manager): def _update_or_create(self, **kwargs): assert kwargs, \ 'update_or_create() must be passed at least one keyword argument' @@ -812,9 +809,9 @@ class AstakosQuotaManager(models.Manager): update_or_create = _update_or_create class AstakosGroupQuota(models.Model): - objects = AstakosQuotaManager() - limit = models.PositiveIntegerField('Limit', null=True) # obsolete field - uplimit = models.BigIntegerField('Up limit', null=True) + objects = ExtendedManager() + limit = models.PositiveIntegerField(_('Limit'), null=True) # obsolete field + uplimit = models.BigIntegerField(_('Up limit'), null=True) resource = models.ForeignKey(Resource) group = models.ForeignKey(AstakosGroup, blank=True) @@ -822,9 +819,9 @@ class AstakosGroupQuota(models.Model): unique_together = ("resource", "group") class AstakosUserQuota(models.Model): - objects = AstakosQuotaManager() - limit = models.PositiveIntegerField('Limit', null=True) # obsolete field - uplimit = models.BigIntegerField('Up limit', null=True) + objects = ExtendedManager() + limit = models.PositiveIntegerField(_('Limit'), null=True) # obsolete field + uplimit = models.BigIntegerField(_('Up limit'), null=True) resource = models.ForeignKey(Resource) user = models.ForeignKey(AstakosUser) @@ -838,8 +835,8 @@ class ApprovalTerms(models.Model): """ date = models.DateTimeField( - 'Issue date', db_index=True, default=datetime.now()) - location = models.CharField('Terms location', max_length=255) + _('Issue date'), db_index=True, default=datetime.now()) + location = models.CharField(_('Terms location'), max_length=255) class Invitation(models.Model): @@ -848,12 +845,12 @@ class Invitation(models.Model): """ inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent', null=True) - realname = models.CharField('Real name', max_length=255) - username = models.CharField('Unique ID', max_length=255, unique=True) - code = models.BigIntegerField('Invitation code', db_index=True) - is_consumed = models.BooleanField('Consumed?', default=False) - created = models.DateTimeField('Creation date', auto_now_add=True) - consumed = models.DateTimeField('Consumption date', null=True, blank=True) + realname = models.CharField(_('Real name'), max_length=255) + username = models.CharField(_('Unique ID'), max_length=255, unique=True) + code = models.BigIntegerField(_('Invitation code'), db_index=True) + is_consumed = models.BooleanField(_('Consumed?'), default=False) + created = models.DateTimeField(_('Creation date'), auto_now_add=True) + consumed = models.DateTimeField(_('Consumption date'), null=True, blank=True) def __init__(self, *args, **kwargs): super(Invitation, self).__init__(*args, **kwargs) @@ -957,14 +954,14 @@ class PendingThirdPartyUser(models.Model): """ Model for registring successful third party user authentications """ - third_party_identifier = models.CharField('Third-party identifier', max_length=255, null=True, blank=True) - provider = models.CharField('Provider', max_length=255, blank=True) + third_party_identifier = models.CharField(_('Third-party identifier'), max_length=255, null=True, blank=True) + provider = models.CharField(_('Provider'), max_length=255, blank=True) email = models.EmailField(_('e-mail address'), blank=True, null=True) first_name = models.CharField(_('first name'), max_length=30, blank=True) last_name = models.CharField(_('last name'), max_length=30, blank=True) affiliation = models.CharField('Affiliation', max_length=255, blank=True) username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters")) - token = models.CharField('Token', max_length=255, null=True, blank=True) + token = models.CharField(_('Token'), max_length=255, null=True, blank=True) created = models.DateTimeField(auto_now_add=True, null=True, blank=True) class Meta: @@ -1003,7 +1000,406 @@ class SessionCatalog(models.Model): session_key = models.CharField(_('session key'), max_length=40) user = models.ForeignKey(AstakosUser, related_name='sessions', null=True) +class MemberAcceptPolicy(models.Model): + policy = models.CharField(_('Policy'), max_length=255, unique=True, db_index=True) + description = models.CharField(_('Description'), max_length=80) + + def __str__(self): + return self.policy + +try: + auto_accept = MemberAcceptPolicy.objects.get(policy='auto_accept') +except: + auto_accept = None + +class ProjectDefinition(models.Model): + name = models.CharField(max_length=80) + homepage = models.URLField(max_length=255, null=True, blank=True) + description = models.TextField(null=True) + start_date = models.DateTimeField() + end_date = models.DateTimeField() + member_accept_policy = models.ForeignKey(MemberAcceptPolicy) + limit_on_members_number = models.PositiveIntegerField(null=True,blank=True) + resource_grants = models.ManyToManyField( + Resource, + null=True, + blank=True, + through='ProjectResourceGrant' + ) + + def save(self): + self.validate_name() + super(ProjectDefinition, self).save() + + def validate_name(self): + """ + Validate name uniqueness among all active projects. + """ + alive_projects = list(get_alive_projects()) + q = filter(lambda p: p.definition.name==self.name, alive_projects) + if q: + raise ValidationError({'name': [_(astakos_messages.UNIQUE_PROJECT_NAME_CONSTRAIN_ERR)]}) + + @property + def violated_resource_grants(self): + return False + + def add_resource_policy(self, service, resource, uplimit, update=True): + """Raises ObjectDoesNotExist, IntegrityError""" + resource = Resource.objects.get(service__name=service, name=resource) + if update: + ResourceGrant.objects.update_or_create( + project=self, + resource=resource, + defaults={'uplimit': uplimit} + ) + else: + q = self.resource_grants_set + q.create(resource=resource, uplimit=uplimit) + + @property + def resource_policies(self): + return self.resource_grants_set.select_related().all() + + @resource_policies.setter + def resource_policies(self, policies): + for p in policies: + service = p.get('service', None) + resource = p.get('resource', None) + uplimit = p.get('uplimit', 0) + update = p.get('update', True) + self.add_resource_policy(service, resource, uplimit, update) + + +class ProjectResourceGrant(models.Model): + objects = ExtendedManager() + member_limit = models.BigIntegerField(null=True) + project_limit = models.BigIntegerField(null=True) + resource = models.ForeignKey(Resource) + project_definition = models.ForeignKey(ProjectDefinition, blank=True) + + class Meta: + unique_together = ("resource", "project_definition") + +class ProjectApplication(models.Model): + serial = models.CharField( + primary_key=True, + max_length=30, + unique=True, + default=uuid.uuid4().hex[:30] + ) + applicant = models.ForeignKey(AstakosUser, related_name='my_project_applications') + owner = models.ForeignKey(AstakosUser, related_name='own_project_applications') + comments = models.TextField(null=True, blank=True) + definition = models.OneToOneField(ProjectDefinition) + issue_date = models.DateTimeField() + precursor_application = models.OneToOneField('ProjectApplication', + null=True, + blank=True + ) + +class Project(models.Model): + serial = models.CharField( + _('username'), + primary_key=True, + max_length=30, + unique=True, + default=uuid.uuid4().hex[:30] + ) + application = models.OneToOneField(ProjectApplication, related_name='project') + creation_date = models.DateTimeField() + last_approval_date = models.DateTimeField() + termination_date = models.DateTimeField() + members = models.ManyToManyField(AstakosUser, through='ProjectMembership') + last_synced_application = models.OneToOneField( + ProjectApplication, related_name='last_project', null=True, blank=True + ) + + @property + def definition(self): + return self.application.definition + + @property + def is_valid(self): + try: + self.application.definition.validate_name() + except ValidationError: + return False + else: + return True + + @property + def is_active(self): + if not self.is_valid: + return False + if not self.last_approval_date: + return False + if self.termination_date: + return False + if self.definition.violated_resource_grants: + return False + return True + + @property + def is_terminated(self): + if not self.is_valid: + return False + if not self.termination_date: + return False + return True + + @property + def is_suspended(self): + if not self.is_valid: + return False + if not self.termination_date: + return False + if not self.last_approval_date: + if not self.definition.violated_resource_grants: + return False + return True + + @property + def is_alive(self): + return self.is_active or self.is_suspended + + @property + def is_inconsistent(self): + now = datetime.now() + if self.creation_date > now: + return True + if self.last_approval_date > now: + return True + if self.terminaton_date > now: + return True + return False + + @property + def approved_members(self): + return self.members.filter(is_accepted=True) + + def suspend(self): + self.last_approval_date = None + self.save() + + def terminate(self): + self.terminaton_date = datetime.now() + self.save() + + def sync(self): + c, rejected = send_quota(self.approved_members) + return rejected + +class ProjectMembership(models.Model): + person = models.ForeignKey(AstakosUser) + project = models.ForeignKey(Project) + issue_date = models.DateField(default=datetime.now()) + decision_date = models.DateField(null=True, db_index=True) + is_accepted = models.BooleanField( + _('Whether the membership application is accepted'), + default=False + ) + + class Meta: + unique_together = ("person", "project") + +def filter_queryset_by_property(q, property): + """ + Incorporate list comprehension for filtering querysets by property + since Queryset.filter() operates on the database level. + """ + return (p for p in q if getattr(p, property, False)) + +def get_alive_projects(): + return filter_queryset_by_property( + Project.objects.all(), + 'is_alive' + ) + +def get_active_projects(): + return filter_queryset_by_property( + Project.objects.all(), + 'is_active' + ) + +def _lookup_object(model, **kwargs): + """ + Returns an object of the specific model matching the given lookup + parameters. + """ + if not kwargs: + raise MissingIdentifier + try: + return model.objects.get(**kwargs) + except model.DoesNotExist: + raise ItemNotExists(model._meta.verbose_name, **kwargs) + except model.MultipleObjectsReturned: + raise MultipleItemsExist(model._meta.verbose_name, **kwargs) + +def _create_object(model, **kwargs): + o = model.objects.create(**kwargs) + o.save() + return o + +def _update_object(model, id, save=True, **kwargs): + o = self._lookup_object(model, id=id) + if kwargs: + o.__dict__.update(kwargs) + if save: + o.save() + return o + +def submit_application(**kwargs): + app = self._create_object(ProjectApplication, **kwargs) + notification = build_notification( + settings.SERVER_EMAIL, + [settings.ADMINS], + _(GROUP_CREATION_SUBJECT) % {'group':app.definition.name}, + _('An new project application identified by %(serial)s has been submitted.') % app.serial + ) + notification.send() + +def list_applications(): + return ProjectAppication.objects.all() + +def create_application(definition, applicant, comments, precursor_application=None, commit=True): + if precursor_application: + application = precursor_application.copy() + application.precursor_application = precursor_application + else: + application = ProjectApplication(owner=applicant) + application.definition = definition + application.applicant = applicant + application.comments = comments + application.issue_date = datetime.now() + if commit: + definition.save() + application.save() + return application + +def approve_application(serial): + app = _lookup_object(ProjectAppication, serial=serial) + notify = False + if not app.precursor_application: + kwargs = { + 'application':app, + 'creation_date':datetime.now(), + 'last_approval_date':datetime.now(), + } + project = _create_object(Project, **kwargs) + else: + project = app.precursor_application.project + last_approval_date = project.last_approval_date + if project.is_valid: + project.application = app + project.last_approval_date = datetime.now() + project.save() + else: + raise Exception(_(astakos_messages.INVALID_PROJECT) % project.__dict__) + + rejected = synchonize_project(project.serial) + if rejected: + # revert to precursor + project.appication = app.precursor_application + if project.application: + project.last_approval_date = last_approval_date + project.save() + rejected = synchonize_project(project.serial) + if rejected: + raise Exception(_(astakos_messages.QH_SYNC_ERROR)) + else: + project.last_application_synced = app + project.save() + sender, recipients, subject, message + notification = build_notification( + settings.SERVER_EMAIL, + [project.owner.email], + _('Project application has been approved on %s alpha2 testing' % SITENAME), + _('Your application request %(serial)s has been apporved.') + ) + notification.send() + + +def list_projects(filter_property=None): + if filter_property: + return filter_queryset_by_property( + Project.objects.all(), + filter_property + ) + return Project.objects.all() + +def add_project_member(serial, user_id, request_user): + project = _lookup_object(Project, serial=serial) + user = _lookup_object(AstakosUser, id=user_id) + if not project.owner == request_user: + raise Exception(_(astakos_messages.NOT_PROJECT_OWNER)) + + if not project.is_alive: + raise Exception(_(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__) + if len(project.members) + 1 > project.limit_on_members_number: + raise Exception(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED)) + m = self._lookup_object(ProjectMembership, person=user, project=project) + if m.is_accepted: + return + m.is_accepted = True + m.decision_date = datetime.now() + m.save() + notification = build_notification( + settings.SERVER_EMAIL, + [user.email], + _('Your membership on project %(name)s has been accepted.') % project.definition.__dict__, + _('Your membership on project %(name)s has been accepted.') % project.definition.__dict__, + ) + notification.send() + +def remove_project_member(serial, user_id, request_user): + project = _lookup_object(Project, serial=serial) + if not project.is_alive: + raise Exception(_(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__) + if not project.owner == request_user: + raise Exception(_(astakos_messages.NOT_PROJECT_OWNER)) + user = self.lookup_user(user_id) + m = _lookup_object(ProjectMembership, person=user, project=project) + if not m.is_accepted: + return + m.is_accepted = False + m.decision_date = datetime.now() + m.save() + notification = build_notification( + settings.SERVER_EMAIL, + [user.email], + _('Your membership on project %(name)s has been removed.') % project.definition.__dict__, + _('Your membership on project %(name)s has been removed.') % project.definition.__dict__ + ) + notification.send() + +def suspend_project(serial): + project = _lookup_object(Project, serial=serial) + project.suspend() + notification = build_notification( + settings.SERVER_EMAIL, + [project.owner.email], + _('Project %(name)s has been suspended.') % project.definition.__dict__, + _('Project %(name)s has been suspended.') % project.definition.__dict__ + ) + notification.send() + +def terminate_project(serial): + project = _lookup_object(Project, serial=serial) + project.termination() + notification = build_notification( + settings.SERVER_EMAIL, + [project.owner.email], + _('Project %(name)s has been terminated.') % project.definition.__dict__, + _('Project %(name)s has been terminated.') % project.definition.__dict__ + ) + notification.send() +def synchonize_project(serial): + project = _lookup_object(Project, serial=serial) + if project.app != project.last_application_synced: + return project.sync() + def create_astakos_user(u): try: AstakosUser.objects.get(user_ptr=u.pk) diff --git a/snf-astakos-app/astakos/im/api/backends/notifications.py b/snf-astakos-app/astakos/im/notifications.py similarity index 69% rename from snf-astakos-app/astakos/im/api/backends/notifications.py rename to snf-astakos-app/astakos/im/notifications.py index cd721792200cc3ecce67c23478102711c7529d6e..4713fdf9931d1f36a10c425125436f1ccf7e21ec 100644 --- a/snf-astakos-app/astakos/im/api/backends/notifications.py +++ b/snf-astakos-app/astakos/im/notifications.py @@ -34,49 +34,15 @@ import logging import socket -from smtplib import SMTPException - from django.conf import settings from django.core.mail import send_mail -from django.template.loader import render_to_string import astakos.im.messages as astakos_messages -from astakos.im.settings import LOGGING_LEVEL - logger = logging.getLogger(__name__) -def _send_admin_notification(template_name, - dictionary=None, - subject='alpha2 testing notification',): - """ - Send notification email to settings.ADMINS. - - Raises SendNotificationError - """ - if not settings.ADMINS: - return - dictionary = dictionary or {} - message = render_to_string(template_name, dictionary) - sender = settings.SERVER_EMAIL - try: - send_mail(subject, - message, sender, [i[1] for i in settings.ADMINS]) - except (SMTPException, socket.error) as e: - logger.exception(e) - raise SendNotificationError() - else: - msg = 'Sent admin notification for user %s' % dictionary - logger.log(LOGGING_LEVEL, msg) - -class EmailNotification(Notification): - def send(self): - send_mail( - subject, - message, - sender, - recipients - ) +def build_notification(): + return EmailNotification(sender, recipients, subject, message) class Notification(object): def __init__(self, sender, recipients, subject, message): @@ -88,6 +54,18 @@ class Notification(object): def send(self): pass +class EmailNotification(Notification): + def send(self): + try: + send_mail( + subject, + message, + sender, + recipients + ) + except: + raise SendNotificationError() + class SendMailError(Exception): pass diff --git a/snf-astakos-app/astakos/im/static/im/css/global.css b/snf-astakos-app/astakos/im/static/im/css/global.css index 709ac4252a2d8efe7884b4e3ed19f89765ea3906..a6bb17072f326a76b4fbb3d261aef67e4b8f2edd 100644 --- a/snf-astakos-app/astakos/im/static/im/css/global.css +++ b/snf-astakos-app/astakos/im/static/im/css/global.css @@ -3,4 +3,4 @@ @import url(colorbox.css); @import url(browser-fixes.css); @import url(forms.css); -@import url(dropkick.css); \ No newline at end of file +/* @import url(dropkick.css); */ \ No newline at end of file diff --git a/snf-astakos-app/astakos/im/templates/im/astakosgroup_detail.html b/snf-astakos-app/astakos/im/templates/im/astakosgroup_detail.html index 0a64e824dc3d581151db25f08d214ebc797bf2ec..ac6d05d67ef623c70cb34f2a38040c73701f64f9 100644 --- a/snf-astakos-app/astakos/im/templates/im/astakosgroup_detail.html +++ b/snf-astakos-app/astakos/im/templates/im/astakosgroup_detail.html @@ -169,7 +169,7 @@ <td>Pending {% if object.is_owner %} <a href="{% url approve_member object.id m.person.id %}?{% if page %}page={{ page }}{% endif %}{% if sorting %}&sorting={{sorting}}{% endif %}">Accept</a> - <a href="{% url disapprove_member object.id m.person.id %}?{% if page %}page={{ page }}{% endif %}{% if sorting %}&sorting={{sorting}}{% endif %}">Reject</a> + <a href="{% url disapprove_member object.id m.person.id %}?{% if page %}page={{ page }}{% endif %}{% if sorting %}&sorting={{sorting}}{% endif %}">Remove</a> {% endif %} </td> {% endif %} diff --git a/snf-astakos-app/astakos/im/templates/im/projects/project_detail.html b/snf-astakos-app/astakos/im/templates/im/projects/project_detail.html new file mode 100644 index 0000000000000000000000000000000000000000..3b2d35f36355dec892133063ec71d0d71792b18a --- /dev/null +++ b/snf-astakos-app/astakos/im/templates/im/projects/project_detail.html @@ -0,0 +1,231 @@ +{% extends "im/account_base.html" %} + +{% load filters %} + +{% block page.body %} +{% with object.owners as owners %} + +<div class="projects"> + + + <h2> + {% if object.is_member %} + <em> + {% if object.is_owner %} + {% if object.is_enabled %} + [ ADMINISTRATOR - ACTIVE ] + {% else %} + [ ADMINISTRATOR - PENDING ] + {% endif %} + {% else %} + {% if object.is_active_member %} + [ ENROLLED - ACTIVE ] + {% else %} + [ ENROLLED - PENDING ] + {% endif %} + {% endif %} + </em> + {% endif %} + <span>{{ object.name|upper }}</span> + </h2> + + <div class="details"> + <h3> + GENERAL INFO + {% if object.is_owner %} + <a href="#" class="edit">EDIT</a> + {% endif %} + </h3> + + <div class="data"> + <p class="restricted">{{ object.desc|safe }}</p> + <dl class="alt-style"> + <dt>Homepage url</dt> + <dd> + {% if object.homepage%} + <a href="{{ object.homepage }}">{{ object.homepage }}</a> + {% else %} + Not set yet + {% endif %} + </dd> + <dt>Moderation</dt> + <dd> + {{ object.moderation_enabled|yesno:"Yes, No" }} + </dd> + </dl> + </div> + <div class="editable" style="display:none;"> + <form action="{% url astakos.im.views.group_detail object.id %}" method="post" + class="withlabels">{% csrf_token %} + {% with update_form as form %} + {% include "im/form_render.html" %} + <div class="form-row submit"> + <input type="submit" class="submit altcol" value="SAVE" /> + </div> + {% endwith %} + </form> + </div> + </div> + <div class="full-dotted"> + <h3>DETAILS</h3> + <dl class="alt-style"> + <dt>Name</dt> + <dd>{{ object.name }} </dd> + <!--<dt>Type</dt> + <dd>{{object.kindname|capfirst}} </dd>--> + <dt>Issue date:</dt> + <dd>{{object.issue_date|date:"d/m/Y"}} </dd> + <dt>Expiration Date</dt> + <dd>{{object.expiration_date|date:"d/m/Y"}} </dd> +<!-- + <dt>Moderation</dt> + <dd>{% if object.moderation_enabled%}Yes{% else %}No{% endif %}</dd> + --> + <dt>Activated</dt> + <dd>{% if object.is_enabled %}Yes{% else %}No{% endif %}</dd> + <dt>Owner</dt> + {{ o.owners }} + <dd>{% for o in owners %} + {% if object.is_owner %} + Me + {% else%} + {{o.realname}} ({{o.email}}) + + {% endif %} + {% endfor %} + </dd> + <dt>Max participants</dt> + <dd>{% if object.max_participants%}{{object.max_participants}}{% else %} {% endif %}</dd> + </dl> + </div> + <div class="full-dotted"> + <h3>RESOURCES</h3> + {% if quota %} + <dl class="alt-style"> + {% for q in quota %} + + <dt> + Max {% if q.is_abbreviation %}{{ q.verbose_name|upper }}{% else %}{{ q.verbose_name }}{% endif %}{% if not q.unit %}s {% endif %} per user + </dt> + <dd> + {% if q.value %} + {% if q.unit %} + {{ q.value|sizeof_fmt }} + {% else %} + {{ q.value|isinf }} + {% endif %} + {% else %} + Unlimited + {% endif %} + </dd> + {% empty %} + No resources + {% endfor %} + + + </dl> + {% else %} + <p>No resources</p> + {% endif %} + </div> + {% if object.is_owner %} + <div class="full-dotted"> + {% with page|concat:sorting as args %} + {% with object.membership_set.select_related.all|paginate:args as membership %} + {% if membership %} + <form method="GET" class="minimal" action="#members-table"> + <div class="form-row"> + <select name="sorting" onchange="this.form.submit();" class="dropkicked"> + <option value="person__email" {% if sorting == 'person__email' %}selected{% endif %}>Sort by User Id</option> + <option value="person__first_name" {% if sorting == 'person__first_name' %}selected{% endif %}>Sort by Name</option> + <option value="date_joined" {% if sorting == 'date_joined' %}selected{% endif %}>Sort by Status</option> + </select> + </div> + </form> + <table class="alt-style" id="members-table"> + <caption>MEMBERS:</caption> + <thead> + <tr> + <th>User Id</th> + <th>Name</th> + <th>Status</th> + </tr> + </thead> + <tbody> + {% for m in membership.object_list %} + <tr> + <td>{{m.person.email}}</td> + <td>{{m.person.realname}}</td> + {% if m.person in owners %} + <td>Owner</td> + {% else %} + {% if m.is_approved %} + <td>Approved + {% if object.is_owner %} + <a href="{% url disapprove_member object.id m.person.id %}?{% if page %}page={{ page }}{% endif %}{% if sorting %}&sorting={{sorting}}{% endif %}">Remove</a> + {% endif %} + </td> + {% else %} + <td>Pending + {% if object.is_owner %} + <a href="{% url approve_member object.id m.person.id %}?{% if page %}page={{ page }}{% endif %}{% if sorting %}&sorting={{sorting}}{% endif %}">Accept</a> + <a href="{% url disapprove_member object.id m.person.id %}?{% if page %}page={{ page }}{% endif %}{% if sorting %}&sorting={{sorting}}{% endif %}">Remove</a> + {% endif %} + </td> + {% endif %} + {% endif %} + </tr> + {% endfor %} + </tbody> + </table> + <div class="pagination"> + <p class="next-prev"> + {% if membership.has_previous %} + <a href="?page={{ membership.previous_page_number }}{% if sorting %}&sorting={{sorting}}{% endif %}">previous</a> + {% endif %} + {% if membership.has_next %} + <a href="?page={{ membership.next_page_number }}{% if sorting %}&sorting={{sorting}}{% endif %}">next</a> + {% endif %} + </p> + <p class="nums"> + <span class="current"> + Page {{ membership.number }} of {{ membership.paginator.num_pages }} + </span> + </p> + </div> + {% else %} + <p>No members yet!</p> + {% endif %} + {% endwith %} + {% endwith %} + </div> + + + + <div class="full-dotted"> + <form action="{% url astakos.im.views.group_detail object.id %}#members-table" method="post" class="withlabels" >{% csrf_token %} + <h2>Enroll more members</h2> + {% with addmembers_form as form %} + {% include "im/form_render.html" %} + {% endwith %} + <div class="form-row submit"> + <input type="submit" class="submit altcol" value="ADD MEMBERS" /> + </div> + </form> + </div> + {% endif %} + <div class="full-dotted"> + <p> + <a href="{% url group_all %}">back to All Projects ></a></li> + </p> + <p> + <a href="{% url group_list %}">back to My Projects ></a> + </p> + </ul> + </div> + + +</div> + +{% endwith %} +{% endblock %} diff --git a/snf-astakos-app/astakos/im/templates/im/projects/project_form_summary.html b/snf-astakos-app/astakos/im/templates/im/projects/project_form_summary.html new file mode 100644 index 0000000000000000000000000000000000000000..4f4a6b02e8282de645bd29d72ea5e384834e94d0 --- /dev/null +++ b/snf-astakos-app/astakos/im/templates/im/projects/project_form_summary.html @@ -0,0 +1,84 @@ +{% extends "im/account_base.html" %} + +{% load filters %} + +{% block page.body %} + +{% with form.data as data %} +<div class="projects summary"> + <form action="{% url group_add_complete %}" method="post" class="quotas-form">{% csrf_token %} + <legend>CONFIRMATION REQUEST</legend> + <P>These are the specifications of the Project you want to create. If you hit the "Submit" button this form will be officially sent to GRNET for review. Please make sure the following reflect exactly your request. After submitting, there is no way to modify your Project request. </P> +<!-- + {% for k,v in data.iteritems %} + <input type="hidden" name="{{ k }}" value="{{ v }}"> + {% endfor %} + --> + {% include "im/form_render.html" %} + <div class="full-dotted"> + <h3>GENERAL INFO</h3> + <p class="restricted">{{ data.desc|safe }}</p> + <dl class="alt-style"> + <dt>Homepage Url</dt> + <dd>{{ data.homepage }} </dd> + <dt>Modaration</dt> + <dd>{{ data.moderation_enabled|yesno:"Yes, No" }}</dd> + </dl> + </div> + <div class="full-dotted"> + <h3>DETAILS</h3> + <dl class="alt-style"> + <dt>Name</dt> + <dd>{{ data.name }} </dd> + <!--<dt>Type</dt> + <dd>Course </dd>--> + <dt>Issue date:</dt> + <dd>{{ data.issue_date|date:"d/m/Y"}} </dd> + <dt>Expiration Date</dt> + <dd>{{ data.expiration_date|date:"d/m/Y"}} </dd> + <dt>Max members per group</dt> + <dd>{% if data.max_participants %}{{ data.max_participants }}{% else %}Unlimited{% endif %}</dd> + </dl> + </div> + + + <div class="full-dotted"> + <h3>RESOURCES</h3> + <p>The following ~okeanos resources will be granted to each member of this Project:</p> + <dl class="alt-style"> + {% for p in policies %} + + <dt> + Max {% if p.is_abbreviation %}{{ p.name|upper }}{% else %}{{ p.name }}{% endif %}{% if not p.unit %}s {% endif %} per user + </dt> + <dd> + {% if p.uplimit %} + {% if p.unit %} + {{ p.uplimit|sizeof_fmt }} + {% else %} + {{ p.uplimit }} + {% endif %} + {% else %} + Unlimited + {% endif %} + </dd> + {% empty %} + No resources + {% endfor %} + </dl> + </div> + + <div class="full-dotted"> + + </div> + + + <div class="form-row submit"> + <input type="submit" value="SUBMIT" class="submit altcol" autocomplete="off"> + </div> + </form> + +</div> +{% endwith %} + +{% endblock %} diff --git a/snf-astakos-app/astakos/im/templates/im/projects/project_list.html b/snf-astakos-app/astakos/im/templates/im/projects/project_list.html new file mode 100644 index 0000000000000000000000000000000000000000..d60ce99858ec499966cfee40f0dd2e54a5850252 --- /dev/null +++ b/snf-astakos-app/astakos/im/templates/im/projects/project_list.html @@ -0,0 +1,362 @@ +{% extends "im/account_base.html" %} + +{% load filters %} + +{% block page.body %} +<div class="maincol {% block innerpage.class %}{% endblock %}"> + <div class="projects"> + <h2>PROJECTS</h2> + {% if form %} + <p>Search for existing Projects and join the ones you like. Please search by Project name. </p> + <form action="{% url group_search %}" method="post" class="withlabels signup submit-inline">{% csrf_token %} + {% include "im/form_render.html" %} + <div class="form-row submit"> + <input type="submit" class="submit altcol" value="SEARCH" /> + {% if q %}<a href="{% url group_all %}">clear</a>{% endif %} + </div> + </form> + + <!--<form action="{% url group_all %}" method="post" class="link-like alone">{% csrf_token %} + <div class="form-row submit"> + <input type="submit" class="submit altcol" value="clear" /> + </div> + </form> + <--> + {% else %} + <div class="two-cols clearfix"> + <div class="rt"> + + </div> + <div class="lt"> + <p>~okeanos gives the opportunity to Greek Academic or Research Organizations/Institutions/Faculty to run their own projects remotely on virtual infrastructure. Simple, fast and with minimal to no cost at all.</p> + <p><a href="{% url how_it_works %}" style="font-size:1.154em;">How it works ></a></p> + </div> + </div> + + + <div class="widjets"> + <!--<a href="#" class="widjet-x" title="remove boxes">X</a>--> + <ul class="clearfix"> + <li class="create"> + <div> + <div class="wrap"> + + <p class="centered"><a href="{% url group_add 'project' %}"><img alt="THINK ABOUT IT" src="/static/im/images/create.png"></a></p> + <p class="txt">Create a new Project in seconds. Specify how many members it will have, which and how many virtual resources it will provide to its members. Describe its purpose. Submit your request and if accepted, you and your colleagues are ready to deploy!<br><br> </p> + <p><a href="{% url group_add 'project' %}">create a project ></a></p> + <!--<p class="btn"><a href="{% url group_create_list %}" class="submit">CREATE</a></p>--> + </div> + </div> + </li> + <li class="join"> + <div> + <div class="wrap"> + <p class="centered"><a href="{% url group_all %}"><img alt="THINK ABOUT IT" src="/static/im/images/join.png"></a></p> + <p class="txt">Become a member of an existing Project and instantly gain access to the resources it has to offer you. Search for open Projects and join for free. Contact the closed Projects administrators, if you think they will accept you. In two words: try to Join now. </p> + + <p><a href="{% url group_all %}">join a project ></a></p> + </div> + </div> + </li> + </ul> + </div> + + {% endif %} + {% with page_obj.object_list as object_list %} + <!-- Search group --> + {% if object_list %} + <div class="full-dotted"> + <form method="GET" class="minimal" action="#searchResults"> + <div class="form-row"> + <select name="sorting" onchange="this.form.submit();" class="dropkicked" tabindex="1"> + <option value="groupname">Sort by Name</option> + <option value="issue_date" {% if sorting == 'issue_date' %}selected{% endif %}>Sort by Issue date</option> + <option value="expiration_date" {% if sorting == 'expiration_date' %}selected{% endif %}>Sort by Expiration Date</option> + <option value="approved_members_num" {% if sorting == 'approved_members_num' %}selected{% endif %}>Sort by Participants</option> + <option value="moderation_enabled" {% if sorting == 'moderation_enabled' %}selected{% endif %}>Sort by Moderation</option> + </select> + <input type="hidden" name="q" value="{{q}}"/> + </div> + </form> + <table class="alt-style complex" id="searchResults"> + <caption> + {% if q %}SEARCH RESULTS{% else %}ALL PROJECTS{% endif %} + </caption> + <thead> + <tr> + <th>Name</th> + <!--<th>Type</th>--> + <th>Issued</th> + <th>Expires</th> + + <th>Enrolled</th> + + + <th>Status</th> + <th> </th> + <th>Moderated</th> + <!-- <th> </th>--> + + </tr> + </thead> + <tbody> + {% for o in object_list %} + <tr class="{% cycle 'tr1' 'tr2' %}"> + <td style="width:22%"><a href="{% url group_detail o.id %}" title="visit group page">{{o.groupname|truncatename}}</a></td> + <!--td>{{o.kindname|capfirst}}</td--> + <td style="width:13%">{{o.issue_date|date:"d/m/Y"}}</td> + <td style="width:13%">{{o.expiration_date|date:"d/m/Y"}}</td> + <td style="width:11%">{{o.approved_members_num}}</td> + + <td style="width:17%"> + <div class="msg-wrap"> + + {% if o.is_member %} + {% if o.membership_approval_date %} + + + {% if not o.is_owner %} + Registered + + {% else %} + Owner + {% endif %} + + + + {% else %} + Activation pending + {% endif %} + {% else %} + Not member + + + {% endif %} + </div> + </td> + <td style="width:15%"> + <div class="msg-wrap"> + + {% if o.is_member %} + {% if o.membership_approval_date %} + + + {% if not o.is_owner %} + + <form action="{% url group_leave o.id %}" method="post" class="link-like">{% csrf_token %} + <input type="submit" value="x leave group" class="leave"/> + </form> + <div class="dialog"> + Are you sure you what to leave this group?<br> + Name: <a href="{% url group_detail o.id %}" title="visit group page">{{o.groupname}}</a><br> + {% if o.desc %}Description:{{o.desc|truncatewords:30}}{% endif %}<br><br> + + <a href="#" class="yes submit">Yes</a> <a href="#" class="no submit">No</a> + </div> + {% else %} + + {% endif %} + + + + {% else %} + + {% endif %} + {% else %} + + <form action="{% url group_join o.id %}" method="post" class="link-like">{% csrf_token %} + <input type="submit" value="+ join group" class="join_group join" /> + </form> + <div class="dialog"> + Are you sure you what to join this group?<br> + Name: <a href="{% url group_detail o.id %}" title="visit group page">{{o.groupname}}</a><br> + {% if o.desc %}Description:{{o.desc|truncatewords:30}}{% endif %}<br><br> + + <a href="#" class="yes submit">Yes</a> <a href="#" class="no submit">No</a> + </div> + + {% endif %} + </div> + </td> + <td class="centered" style="width:9%">{% if o.moderation_enabled%}Yes{% else %}No{% endif %}</td> + <!--td><a href="#" class="more-info" title="more info">+ more info</a></td--> + </tr> + <tr class="{% cycle 'tmore1' 'tmore2' %}" style="display:none"> + <td colspan="7" class="info-td"> + <div> + <p>{{o.desc}}</p> + <p>{% if o.homepage%} + Group's home page: <a target="_blank" href="{{ o.homepage }}">{{ o.homepage }}</a> + + {% endif %} + </p> + </div> + </td> + </tr> + {% endfor %} + </tbody> + </table> + + </div> + + + <div class="pagination"> + <p class="next-prev"> + {% if page_obj.has_previous %} + <a href="?page={{ page_obj.previous_page_number }}{% if q %}&q={{q}}{% endif %}{% if sorting %}&sorting={{sorting}}{% endif %}">previous</a> + {% endif %} + {% if page_obj.has_next %} + <a href="?page={{ page_obj.next_page_number }}{% if q %}&q={{q}}{% endif %}{% if sorting %}&sorting={{sorting}}{% endif %}">next</a> + {% endif %} + </p> + <p class="nums"> + <span class="current"> + Page {{ page_obj.number }} of {{ paginator.num_pages }} + </span> + </p> + </div> + <!-- Group listing --> + {% else %} + {% if not form %} + {% with page|concat:sorting as args %} + {% with q|paginate:args as page_obj %} + {% if page_obj.object_list %} + <div> + <form method="GET" class="minimal" action="#allGroups" id="mygroups"> + <div class="form-row"> + <select name="sorting" class="dropkicked" tabindex="1"> + <option value="groupname">Sort by Name</option> + <!--<option value="kindname" {% if sorting == 'kindname' %}selected{% endif %}>Type</option>--> + <option value="issue_date" {% if sorting == 'issue_date' %}selected{% endif %}>Sort by Issue date</option> + <option value="expiration_date" {% if sorting == 'expiration_date' %}selected{% endif %}>Sort by Expiration Date</option> + <option value="approved_members_num" {% if sorting == 'approved_members_num' %}selected{% endif %}>Sort by Participants</option> + <option value="moderation_enabled" {% if sorting == 'moderation_enabled' %}selected{% endif %}>Sort by Moderation</option> + </select> + </div> + </form> + <table class="alt-style complex" id="allGroups"> + <caption>MY PROJECTS</caption> + <thead> + <tr> + <th>Name</th> + <!--th>Type</th--> + <th>Issued</th> + <th>Expires</th> + <th>Enrolled</th> + <th>Status</th> + <th> </th> + <th class="centered">Moderated</th> + <!-- <th> </th>--> + + </tr> + </thead> + <tbody> + {% for o in page_obj.object_list %} + <tr class="{% cycle 'tr1' 'tr2' %}"> + <td style="width:22%"><a href="{% url group_detail o.id %}" title="visit group page">{{o.groupname|truncatename }}</a></td> + <!--td>{{o.kindname|capfirst}}</td--> + <td style="width:13%">{{o.issue_date|date:"d/m/Y"}}</td> + <td style="width:13%">{{o.expiration_date|date:"d/m/Y"}}</td> + <td style="width:11%">{{ o.approved_members_num }}</td> + <td style="width:17%"> + <div class="msg-wrap"> + {% if user.email = o.groupowner %} + {% if o.is_enabled %} + Active (Owner) + {% else %} + Pending + {% endif %} + {% else %} + {% if o.is_enabled %} + {% if o.membership_status %} + Registered + + {% else %} + Activation Pending + {% endif %} + {% else %} + - + {% endif %} + {% endif %} + + </div> + </td> + <td style="width:15%"> + <div class="msg-wrap"> + {% if user.email = o.groupowner %} + {% if o.is_enabled %} + + {% else %} + + {% endif %} + {% else %} + {% if o.is_enabled %} + {% if o.membership_status %} + + <form action="{% url group_leave o.id %}" method="post" class="link-like">{% csrf_token %} + <input type="submit" value="x leave" class="leave" /> + </form> + <div class="dialog"> + Are you sure you what to leave this group?<br> + Name: <a href="{% url group_detail o.id %}" title="visit group page">{{o.groupname}}</a><br> + {% if o.desc %}Description:{{o.desc|truncatewords:30}}{% endif %}<br><br> + + <a href="#" class="yes submit">Yes</a> <a href="#" class="no submit">No</a> + </div> + {% else %} + + {% endif %} + {% else %} + + {% endif %} + {% endif %} + + </div> + </td> + <td class="centered" style="width:9%">{% if o.moderation_enabled%}Yes{% else %}No{% endif %}</td> + <!-- <td><a href="#" class="more-info" title="more info">+ more info </a></td>--> + </tr> + <tr class="{% cycle 'tmore1' 'tmore2' %}" style="display:none"> + <td colspan="8" class="info-td"> + <div> + <p>{{o.desc}}</p> + <p>{% if o.homepage%} + Group's home page: <a href="{{ o.homepage }}">{{ o.homepage }}</a> + {% endif %} + </p> + </div> + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + <div class="pagination"> + <p class="next-prev"> + {% if page_obj.has_previous %} + <a href="?page={{ page_obj.previous_page_number }}{% if sorting %}&sorting={{ sorting }}{% endif%}#allGroups">previous</a> + {% endif %} + {% if page_obj.has_next %} + <a href="?page={{ page_obj.next_page_number }}{% if sorting %}&sorting={{ sorting }}{% endif%}#allGroups">next</a> + {% endif %} + </p> + <p class="nums"> + <span class="current"> + Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }} + </span> + </p> + </div> + {% endif %} + {% endwith %} + {% endwith %} + {% endif %} + {% if form %} + {% if q %} + <h2>No projects found!</h2> + {% endif %} + {% endif %} + {% endif %} + {% endwith %} +</div> +</div> +{% endblock %} diff --git a/snf-astakos-app/astakos/im/templates/im/projects/projectapplication_detail.html b/snf-astakos-app/astakos/im/templates/im/projects/projectapplication_detail.html new file mode 100644 index 0000000000000000000000000000000000000000..3b2d35f36355dec892133063ec71d0d71792b18a --- /dev/null +++ b/snf-astakos-app/astakos/im/templates/im/projects/projectapplication_detail.html @@ -0,0 +1,231 @@ +{% extends "im/account_base.html" %} + +{% load filters %} + +{% block page.body %} +{% with object.owners as owners %} + +<div class="projects"> + + + <h2> + {% if object.is_member %} + <em> + {% if object.is_owner %} + {% if object.is_enabled %} + [ ADMINISTRATOR - ACTIVE ] + {% else %} + [ ADMINISTRATOR - PENDING ] + {% endif %} + {% else %} + {% if object.is_active_member %} + [ ENROLLED - ACTIVE ] + {% else %} + [ ENROLLED - PENDING ] + {% endif %} + {% endif %} + </em> + {% endif %} + <span>{{ object.name|upper }}</span> + </h2> + + <div class="details"> + <h3> + GENERAL INFO + {% if object.is_owner %} + <a href="#" class="edit">EDIT</a> + {% endif %} + </h3> + + <div class="data"> + <p class="restricted">{{ object.desc|safe }}</p> + <dl class="alt-style"> + <dt>Homepage url</dt> + <dd> + {% if object.homepage%} + <a href="{{ object.homepage }}">{{ object.homepage }}</a> + {% else %} + Not set yet + {% endif %} + </dd> + <dt>Moderation</dt> + <dd> + {{ object.moderation_enabled|yesno:"Yes, No" }} + </dd> + </dl> + </div> + <div class="editable" style="display:none;"> + <form action="{% url astakos.im.views.group_detail object.id %}" method="post" + class="withlabels">{% csrf_token %} + {% with update_form as form %} + {% include "im/form_render.html" %} + <div class="form-row submit"> + <input type="submit" class="submit altcol" value="SAVE" /> + </div> + {% endwith %} + </form> + </div> + </div> + <div class="full-dotted"> + <h3>DETAILS</h3> + <dl class="alt-style"> + <dt>Name</dt> + <dd>{{ object.name }} </dd> + <!--<dt>Type</dt> + <dd>{{object.kindname|capfirst}} </dd>--> + <dt>Issue date:</dt> + <dd>{{object.issue_date|date:"d/m/Y"}} </dd> + <dt>Expiration Date</dt> + <dd>{{object.expiration_date|date:"d/m/Y"}} </dd> +<!-- + <dt>Moderation</dt> + <dd>{% if object.moderation_enabled%}Yes{% else %}No{% endif %}</dd> + --> + <dt>Activated</dt> + <dd>{% if object.is_enabled %}Yes{% else %}No{% endif %}</dd> + <dt>Owner</dt> + {{ o.owners }} + <dd>{% for o in owners %} + {% if object.is_owner %} + Me + {% else%} + {{o.realname}} ({{o.email}}) + + {% endif %} + {% endfor %} + </dd> + <dt>Max participants</dt> + <dd>{% if object.max_participants%}{{object.max_participants}}{% else %} {% endif %}</dd> + </dl> + </div> + <div class="full-dotted"> + <h3>RESOURCES</h3> + {% if quota %} + <dl class="alt-style"> + {% for q in quota %} + + <dt> + Max {% if q.is_abbreviation %}{{ q.verbose_name|upper }}{% else %}{{ q.verbose_name }}{% endif %}{% if not q.unit %}s {% endif %} per user + </dt> + <dd> + {% if q.value %} + {% if q.unit %} + {{ q.value|sizeof_fmt }} + {% else %} + {{ q.value|isinf }} + {% endif %} + {% else %} + Unlimited + {% endif %} + </dd> + {% empty %} + No resources + {% endfor %} + + + </dl> + {% else %} + <p>No resources</p> + {% endif %} + </div> + {% if object.is_owner %} + <div class="full-dotted"> + {% with page|concat:sorting as args %} + {% with object.membership_set.select_related.all|paginate:args as membership %} + {% if membership %} + <form method="GET" class="minimal" action="#members-table"> + <div class="form-row"> + <select name="sorting" onchange="this.form.submit();" class="dropkicked"> + <option value="person__email" {% if sorting == 'person__email' %}selected{% endif %}>Sort by User Id</option> + <option value="person__first_name" {% if sorting == 'person__first_name' %}selected{% endif %}>Sort by Name</option> + <option value="date_joined" {% if sorting == 'date_joined' %}selected{% endif %}>Sort by Status</option> + </select> + </div> + </form> + <table class="alt-style" id="members-table"> + <caption>MEMBERS:</caption> + <thead> + <tr> + <th>User Id</th> + <th>Name</th> + <th>Status</th> + </tr> + </thead> + <tbody> + {% for m in membership.object_list %} + <tr> + <td>{{m.person.email}}</td> + <td>{{m.person.realname}}</td> + {% if m.person in owners %} + <td>Owner</td> + {% else %} + {% if m.is_approved %} + <td>Approved + {% if object.is_owner %} + <a href="{% url disapprove_member object.id m.person.id %}?{% if page %}page={{ page }}{% endif %}{% if sorting %}&sorting={{sorting}}{% endif %}">Remove</a> + {% endif %} + </td> + {% else %} + <td>Pending + {% if object.is_owner %} + <a href="{% url approve_member object.id m.person.id %}?{% if page %}page={{ page }}{% endif %}{% if sorting %}&sorting={{sorting}}{% endif %}">Accept</a> + <a href="{% url disapprove_member object.id m.person.id %}?{% if page %}page={{ page }}{% endif %}{% if sorting %}&sorting={{sorting}}{% endif %}">Remove</a> + {% endif %} + </td> + {% endif %} + {% endif %} + </tr> + {% endfor %} + </tbody> + </table> + <div class="pagination"> + <p class="next-prev"> + {% if membership.has_previous %} + <a href="?page={{ membership.previous_page_number }}{% if sorting %}&sorting={{sorting}}{% endif %}">previous</a> + {% endif %} + {% if membership.has_next %} + <a href="?page={{ membership.next_page_number }}{% if sorting %}&sorting={{sorting}}{% endif %}">next</a> + {% endif %} + </p> + <p class="nums"> + <span class="current"> + Page {{ membership.number }} of {{ membership.paginator.num_pages }} + </span> + </p> + </div> + {% else %} + <p>No members yet!</p> + {% endif %} + {% endwith %} + {% endwith %} + </div> + + + + <div class="full-dotted"> + <form action="{% url astakos.im.views.group_detail object.id %}#members-table" method="post" class="withlabels" >{% csrf_token %} + <h2>Enroll more members</h2> + {% with addmembers_form as form %} + {% include "im/form_render.html" %} + {% endwith %} + <div class="form-row submit"> + <input type="submit" class="submit altcol" value="ADD MEMBERS" /> + </div> + </form> + </div> + {% endif %} + <div class="full-dotted"> + <p> + <a href="{% url group_all %}">back to All Projects ></a></li> + </p> + <p> + <a href="{% url group_list %}">back to My Projects ></a> + </p> + </ul> + </div> + + +</div> + +{% endwith %} +{% endblock %} diff --git a/snf-astakos-app/astakos/im/templates/im/projects/projectapplication_form.html b/snf-astakos-app/astakos/im/templates/im/projects/projectapplication_form.html new file mode 100644 index 0000000000000000000000000000000000000000..a3665e695676c51aeba322a5c2012e12184db3e4 --- /dev/null +++ b/snf-astakos-app/astakos/im/templates/im/projects/projectapplication_form.html @@ -0,0 +1,102 @@ +{% extends "im/account_base.html" %} + +{% load filters %} +{% block headjs %} + {{ block.super }} + <script src="{{ IM_STATIC_URL }}js/quotas.js"></script> +{% endblock %} +{% block page.body %} +<form action="#top" method="post" class="withlabels quotas-form" id="group_create_form">{% csrf_token %} + + <fieldset class="with-info" id="top"> + <legend> + 1. CREATE GROUP + <span class="info"> + <em>more info</em> + <span>Fill in the required fields to create a group. Group details will be visible to the users of the group.</span> + </span> + </legend> + + {% include "im/form_render.html" %} + + </fieldset> + + <fieldset id="icons"> + <legend> + 2. ADD RESOURCES + <span class="info"> + <em>more info</em> + <span>You need to specify at least one resource</span> + </span> + </legend> + <ul class="clearfix"> + {% for g, group_info in resource_catalog.groups.items %} + {% if g %} + <li> + <a href="#{{ g }}" id="{{'group_'|add:g}}"><img src="/static/im/images/create-{{ g }}.png" alt="vm"/></a> + <input type="hidden" name="proxy_{{ 'is_selected_'|add:g }}" id="proxy_{{ 'id_is_selected_'|add:g }}"> + <p class="msg">{{ group_info.help_text }}</p> + </li> + {% endif %} + {% endfor %} + </ul> + + </fieldset> + + <div class="visible"> + + </div> + <div class="not-visible"> + {% for gname, resources in resource_catalog.get_groups_resources %} + <div class="group {{'group_'|add:gname}}" id="{{ gname }}"> + <a href="#icons" class="delete">X remove resource</a> + {% for rname, rdata in resources.items %} + <fieldset class="quota"> + + <legend> + {% if rdata.is_abbreviation %} + {{ rdata.verbose_name|upper }} + {% else %} + {{ rdata.verbose_name|capfirst }} + {% endif %} + <span class="info"> + <em>more info</em> + <span>{{ rdata.help_text }}</span> + </span> + </legend> + <div class="form-row"> + <p class="clearfix"> + <label for="{{'id_'|add:rname|add:'_uplimit'}}_proxy" > + Max {% if rdata.is_abbreviation %}{{ rdata.verbose_name|upper }}{% else %}{{ rdata.verbose_name }}{% endif %}{% if not rdata.unit %}s {% endif %} per user + </label> + <input type="text" + id="{{'id_'|add:rname|add:'_uplimit'}}_proxy" + name="{{rname|add:'_uplimit'}}_proxy" + placeholder="{{ rdata.placeholder}} " + {% if rdata.unit == 'bytes' %} + class="dehumanize" + {% endif %} + /> + <span class="extra-img"> </span> + <span class="info"><em>more info</em><span>Leave this field blank if you don't want to specify this resource</span></span> + <p class="error-msg">Invalid format</p> + </p> + <p class="msg"></p> + </div> + </fieldset> + {% endfor %} + </div> + + {% endfor %} + </div> + <div class="form-row submit"> + <input type="submit" value="CONTINUE" class="submit altcol" autocomplete="off"> + </div> + <input type="hidden" name="user" id="id_user" value="{{user.id}}"> +</form> + +<script> + +</script> + +{% endblock %} \ No newline at end of file diff --git a/snf-astakos-app/astakos/im/templates/im/projects/projectapplication_list.html b/snf-astakos-app/astakos/im/templates/im/projects/projectapplication_list.html new file mode 100644 index 0000000000000000000000000000000000000000..0b314859cbad184c10cd162bd6f895a32f7b135e --- /dev/null +++ b/snf-astakos-app/astakos/im/templates/im/projects/projectapplication_list.html @@ -0,0 +1,14 @@ +{% extends "im/account_base.html" %} + +{% load filters %} + +{% block page.body %} +<div class="maincol {% block innerpage.class %}{% endblock %}"> + <div class="projects"> + <h2>PROJECTS</h2> + {% with page_obj.object_list as object_list %} + {{object_list}} + {% endwith %} + </div> +</div> +{% endblock %} diff --git a/snf-astakos-app/astakos/im/urls.py b/snf-astakos-app/astakos/im/urls.py index 89047741353a7706db160eee8b5465ad81b879e0..96d7ef4220a664ed84f34d9402f8db14edfacbb7 100644 --- a/snf-astakos-app/astakos/im/urls.py +++ b/snf-astakos-app/astakos/im/urls.py @@ -50,8 +50,10 @@ urlpatterns = patterns('astakos.im.views', url(r'^approval_terms/(?P<term_id>\d+)/?$', 'approval_terms'), url(r'^send/activation/(?P<user_id>\d+)/?$', 'send_activation', {}, name='send_activation'), url(r'^resources/?$', 'resource_usage', {}, name='resource_usage'), + # url(r'^billing/?$', 'billing', {}, name='billing'), - url(r'^timeline/?$', 'timeline', {}, name='timeline'), +# url(r'^timeline/?$', 'timeline', {}, name='timeline'), + url(r'^group/add/complete/?$', 'group_add_complete', {}, name='group_add_complete'), url(r'^group/add/(?P<kind_name>\w+)?$', 'group_add', {}, name='group_add'), url(r'^group/list/?$', 'group_list', {}, name='group_list'), @@ -63,9 +65,21 @@ urlpatterns = patterns('astakos.im.views', url(r'^group/(?P<group_id>\d+)/(?P<user_id>\d+)/approve/?$', 'approve_member', {}, name='approve_member'), url(r'^group/(?P<group_id>\d+)/(?P<user_id>\d+)/disapprove/?$', 'disapprove_member', {}, name='disapprove_member'), url(r'^group/create/?$', 'group_create_list', {}, name='group_create_list'), - url(r'^remove_auth_provider/(?P<pk>\d+)?$', 'remove_auth_provider', {}, - name='remove_auth_provider'), - url(r'^group/how_it_works/?$', 'how_it_works', {}, name='how_it_works') + url(r'^group/how_it_works/?$', 'how_it_works', {}, name='how_it_works'), + + url(r'^project/add/?$', 'project_add', {}, name='project_add'), + url(r'^project/application/list/?$', 'project_application_list', {}, name='project_application_list'), + url(r'^project/list/?$', 'project_list', {}, name='project_list'), + url(r'^project/(?P<serial>\w+)/?$', 'project_detail', {}, name='project_detail'), + url(r'^project/search/?$', 'project_search', {}, name='project_search'), + url(r'^project/all/?$', 'project_all', {}, name='project_all'), + url(r'^project/(?P<serial>\w+)/join/?$', 'project_join', {}, name='project_join'), + url(r'^project/(?P<serial>\w+)/leave/?$', 'project_leave', {}, name='project_leave'), + url(r'^project/(?P<serial>\w+)/(?P<user_id>\d+)/approve/?$', 'project_approve_member', {}, name='project_approve_member'), + url(r'^project/(?P<serial>\w+)/(?P<user_id>\d+)/disapprove/?$', 'project_remove_member', {}, name='project_remove_member'), + + url(r'^group/how_it_works/?$', 'how_it_works', {}, name='how_it_works'), + url(r'^remove_auth_provider/(?P<pk>\d+)?$', 'remove_auth_provider', {}, name='remove_auth_provider'), ) diff --git a/snf-astakos-app/astakos/im/views.py b/snf-astakos-app/astakos/im/views.py index be905e529a3c0c20ffb7448a612c71dddc29ec2d..87126a40da64911bf1024eedeec2875e80347526 100644 --- a/snf-astakos-app/astakos/im/views.py +++ b/snf-astakos-app/astakos/im/views.py @@ -53,8 +53,9 @@ from django.shortcuts import redirect from django.template import RequestContext, loader as template_loader from django.utils.http import urlencode from django.utils.translation import ugettext as _ -from django.views.generic.create_update import (delete_object, - get_model_and_form_class) +from django.views.generic.create_update import ( + create_object, delete_object, get_model_and_form_class +) from django.views.generic.list_detail import object_list from django.core.xheaders import populate_xheaders from django.core.exceptions import ValidationError, PermissionDenied @@ -63,29 +64,36 @@ from django.views.decorators.http import require_http_methods from django.db.models import Q from astakos.im.activation_backends import get_backend, SimpleBackend -from astakos.im.models import (AstakosUser, ApprovalTerms, AstakosGroup, - EmailChange, GroupKind, Membership, - RESOURCE_SEPARATOR, AstakosUserAuthProvider) +from astakos.im.models import ( + AstakosUser, ApprovalTerms, AstakosGroup, + EmailChange, GroupKind, Membership, + RESOURCE_SEPARATOR, AstakosUserAuthProvider, + ProjectApplication +) from astakos.im.util import get_context, prepare_response, get_query, restrict_next -from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm, - FeedbackForm, SignApprovalTermsForm, - EmailChangeForm, - AstakosGroupCreationForm, AstakosGroupSearchForm, - AstakosGroupUpdateForm, AddGroupMembersForm, - MembersSortForm, AstakosGroupSortForm, - TimelineForm, PickResourceForm, - AstakosGroupCreationSummaryForm) +from astakos.im.forms import ( + LoginForm, InvitationForm, ProfileForm, + FeedbackForm, SignApprovalTermsForm, + EmailChangeForm, + AstakosGroupCreationForm, AstakosGroupSearchForm, + AstakosGroupUpdateForm, AddGroupMembersForm, + MembersSortForm, AstakosGroupSortForm, + TimelineForm, PickResourceForm, + AstakosGroupCreationSummaryForm, + ProjectApplicationForm +) from astakos.im.functions import (send_feedback, SendMailError, logout as auth_logout, activate as activate_func, send_activation as send_activation_func, - send_group_creation_notification, +# send_group_creation_notification, SendNotificationError) from astakos.im.endpoints.qh import timeline_charge from astakos.im.settings import (COOKIE_DOMAIN, LOGOUT_NEXT, LOGGING_LEVEL, PAGINATE_BY, RESOURCES_PRESENTATION_DATA, PAGINATE_BY_ALL) #from astakos.im.tasks import request_billing from astakos.im.api.callpoint import AstakosCallpoint +# from .generic_views import create_object import astakos.im.messages as astakos_messages from astakos.im import settings @@ -802,7 +810,6 @@ class ResourcePresentation(): @signed_terms_required @login_required def group_add(request, kind_name='default'): - result = callpoint.list_resources() resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA) resource_catalog.update_from_result(result) @@ -863,10 +870,11 @@ def group_add(request, kind_name='default'): return HttpResponse(t.render(c)) -#@require_http_methods(["POST"]) +#@require_hsttp_methods(["POST"]) @require_http_methods(["GET", "POST"]) @signed_terms_required @login_required +@transaction.commit_manually def group_add_complete(request): model = AstakosGroup form = AstakosGroupCreationSummaryForm(request.POST) @@ -876,10 +884,6 @@ def group_add_complete(request): result = callpoint.create_groups((d,)).next() if result.is_success: new_object = result.data[0] - msg = _(astakos_messages.OBJECT_CREATED) %\ - {"verbose_name": model._meta.verbose_name} - messages.success(request, msg, fail_silently=True) - # send notification try: send_group_creation_notification( @@ -892,8 +896,13 @@ def group_add_complete(request): ) except SendNotificationError, e: messages.error(request, e, fail_silently=True) - post_save_redirect = '/im/group/%(id)s/' - return HttpResponseRedirect(post_save_redirect % new_object) + transaction.rollback() + else: + msg = _(astakos_messages.OBJECT_CREATED) %\ + {"verbose_name": model._meta.verbose_name} + message = _(astakos_messages.NOTIFICATION_SENT) % 'a project' + messages.success(request, msg, fail_silently=True) + transaction.commit() else: d = {"verbose_name": model._meta.verbose_name, "reason":result.reason} @@ -1389,35 +1398,35 @@ def group_create_list(request): # return data -#@require_http_methods(["GET"]) -@require_http_methods(["POST", "GET"]) -@signed_terms_required -@login_required -def timeline(request): -# data = {'entity':request.user.email} - timeline_body = () - timeline_header = () -# form = TimelineForm(data) - form = TimelineForm() - if request.method == 'POST': - data = request.POST - form = TimelineForm(data) - if form.is_valid(): - data = form.cleaned_data - timeline_header = ('entity', 'resource', - 'event name', 'event date', - 'incremental cost', 'total cost') - timeline_body = timeline_charge( - data['entity'], data['resource'], - data['start_date'], data['end_date'], - data['details'], data['operation']) - - return render_response(template='im/timeline.html', - context_instance=get_context(request), - form=form, - timeline_header=timeline_header, - timeline_body=timeline_body) - return data +# #@require_http_methods(["GET"]) +# @require_http_methods(["POST", "GET"]) +# @signed_terms_required +# @login_required +# def timeline(request): +# # data = {'entity':request.user.email} +# timeline_body = () +# timeline_header = () +# # form = TimelineForm(data) +# form = TimelineForm() +# if request.method == 'POST': +# data = request.POST +# form = TimelineForm(data) +# if form.is_valid(): +# data = form.cleaned_data +# timeline_header = ('entity', 'resource', +# 'event name', 'event date', +# 'incremental cost', 'total cost') +# timeline_body = timeline_charge( +# data['entity'], data['resource'], +# data['start_date'], data['end_date'], +# data['details'], data['operation']) +# +# return render_response(template='im/timeline.html', +# context_instance=get_context(request), +# form=form, +# timeline_header=timeline_header, +# timeline_body=timeline_body) +# return data # TODO: action only on POST and user should confirm the removal @@ -1441,4 +1450,83 @@ def how_it_works(request): return render_response( template='im/how_it_works.html', context_instance=get_context(request),) + +@require_http_methods(["GET", "POST"]) +@signed_terms_required +@login_required +def project_add(request): + result = callpoint.list_resources() + resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA) + resource_catalog.update_from_result(result) + + if not result.is_success: + messages.error( + request, + 'Unable to retrieve system resources: %s' % result.reason + ) + extra_context = {'resource_catalog':resource_catalog} + return create_object(request, template_name='im/projects/projectapplication_form.html', + extra_context=extra_context, post_save_redirect=request.path, + form_class=ProjectApplicationForm) + +@require_http_methods(["GET"]) +@signed_terms_required +@login_required +def project_application_list(request): + queryset = ProjectApplication.objects.all() + return object_list( + request, + queryset, + paginate_by=PAGINATE_BY_ALL, + page=request.GET.get('page') or 1, + template_name='im/projects/projectapplication_list.html') + + +@require_http_methods(["GET"]) +@signed_terms_required +@login_required +def project_list(request): + pass + +@require_http_methods(["GET", "POST"]) +@signed_terms_required +@login_required +def project_detail(request, serial): + pass + +@require_http_methods(["GET", "POST"]) +@signed_terms_required +@login_required +def project_search(request): + pass + +@require_http_methods(["GET"]) +@signed_terms_required +@login_required +def project_all(request): + pass + +@require_http_methods(["GET", "POST"]) +@signed_terms_required +@login_required +def project_join(request, serial): + pass + +@require_http_methods(["GET", "POST"]) +@signed_terms_required +@login_required +def project_leave(request, serial): + pass + +@require_http_methods(["POST"]) +@signed_terms_required +@login_required +def project_approve_member(request, serial, user_id): + pass + +@require_http_methods(["POST"]) +@signed_terms_required +@login_required +def project_remove_member(request, serial, user_id): + pass \ No newline at end of file