Merge remote-tracking branch 'origin/0.12' into devel-0.13
authorSofia Papagiannaki <papagian@gmail.com>
Thu, 29 Nov 2012 15:08:26 +0000 (17:08 +0200)
committerSofia Papagiannaki <papagian@gmail.com>
Thu, 29 Nov 2012 15:08:26 +0000 (17:08 +0200)
Conflicts:
snf-astakos-app/astakos/im/activation_backends.py
snf-astakos-app/astakos/im/api/admin.py
snf-astakos-app/astakos/im/auth_backends.py
snf-astakos-app/astakos/im/forms.py
snf-astakos-app/astakos/im/functions.py
snf-astakos-app/astakos/im/management/commands/group-list.py
snf-astakos-app/astakos/im/management/commands/invitation-list.py
snf-astakos-app/astakos/im/management/commands/service-list.py
snf-astakos-app/astakos/im/management/commands/user-add.py
snf-astakos-app/astakos/im/management/commands/user-list.py
snf-astakos-app/astakos/im/middleware.py
snf-astakos-app/astakos/im/models.py
snf-astakos-app/astakos/im/settings.py
snf-astakos-app/astakos/im/target/local.py
snf-astakos-app/astakos/im/target/redirect.py
snf-astakos-app/astakos/im/target/shibboleth.py
snf-astakos-app/astakos/im/urls.py
snf-astakos-app/astakos/im/util.py
snf-astakos-app/astakos/im/views.py
snf-astakos-app/conf/20-snf-astakos-app-settings.conf~

188 files changed:
.gitignore
config [moved from snf-astakos-app/astakos/im/queue/__init__.py with 100% similarity]
snf-astakos-app/.settings/org.eclipse.core.resources.prefs [new file with mode: 0644]
snf-astakos-app/Changelog
snf-astakos-app/README
snf-astakos-app/astakos/__init__.py
snf-astakos-app/astakos/im/activation_backends.py
snf-astakos-app/astakos/im/api/__init__.py
snf-astakos-app/astakos/im/api/admin.py
snf-astakos-app/astakos/im/api/backends/__init__.py [moved from snf-astakos-app/astakos/im/queue/listener.py with 81% similarity]
snf-astakos-app/astakos/im/api/backends/base.py [new file with mode: 0644]
snf-astakos-app/astakos/im/api/backends/errors.py [new file with mode: 0644]
snf-astakos-app/astakos/im/api/backends/lib/__init__.py [new file with mode: 0644]
snf-astakos-app/astakos/im/api/backends/lib/django/__init__.py [new file with mode: 0644]
snf-astakos-app/astakos/im/api/backends/notifications.py [new file with mode: 0644]
snf-astakos-app/astakos/im/api/callpoint.py [new file with mode: 0644]
snf-astakos-app/astakos/im/api/client.py [new file with mode: 0644]
snf-astakos-app/astakos/im/api/faults.py
snf-astakos-app/astakos/im/api/service.py
snf-astakos-app/astakos/im/api/spec.py [new file with mode: 0644]
snf-astakos-app/astakos/im/auth_backends.py
snf-astakos-app/astakos/im/context_processors.py
snf-astakos-app/astakos/im/cookie.py
snf-astakos-app/astakos/im/endpoints/__init__.py [new file with mode: 0644]
snf-astakos-app/astakos/im/endpoints/aquarium/__init__.py [new file with mode: 0644]
snf-astakos-app/astakos/im/endpoints/aquarium/client.py [moved from snf-astakos-app/astakos/im/queue/userevent.py with 66% similarity]
snf-astakos-app/astakos/im/endpoints/aquarium/consumer.py [new file with mode: 0644]
snf-astakos-app/astakos/im/endpoints/aquarium/producer.py [new file with mode: 0644]
snf-astakos-app/astakos/im/endpoints/qh.py [new file with mode: 0644]
snf-astakos-app/astakos/im/fixtures/groups.json [deleted file]
snf-astakos-app/astakos/im/forms.py
snf-astakos-app/astakos/im/functions.py
snf-astakos-app/astakos/im/management/commands/_common.py
snf-astakos-app/astakos/im/management/commands/group-add.py
snf-astakos-app/astakos/im/management/commands/group-list.py
snf-astakos-app/astakos/im/management/commands/group-permissions-add.py
snf-astakos-app/astakos/im/management/commands/group-permissions-remove.py~HEAD [moved from snf-astakos-app/astakos/im/management/commands/group-permissions-remove.py with 87% similarity]
snf-astakos-app/astakos/im/management/commands/group-permissions-remove.py~future [new file with mode: 0644]
snf-astakos-app/astakos/im/management/commands/group-update.py [new file with mode: 0644]
snf-astakos-app/astakos/im/management/commands/invitation-details.py [moved from snf-astakos-app/astakos/im/management/commands/invitation-show.py with 98% similarity]
snf-astakos-app/astakos/im/management/commands/invitation-list.py
snf-astakos-app/astakos/im/management/commands/quotaholder-sync.py [new file with mode: 0644]
snf-astakos-app/astakos/im/management/commands/resource-add.py [new file with mode: 0644]
snf-astakos-app/astakos/im/management/commands/resource-list.py [new file with mode: 0644]
snf-astakos-app/astakos/im/management/commands/resource-remove.py [new file with mode: 0644]
snf-astakos-app/astakos/im/management/commands/service-add.py
snf-astakos-app/astakos/im/management/commands/service-list.py
snf-astakos-app/astakos/im/management/commands/service-remove.py
snf-astakos-app/astakos/im/management/commands/service-token-renew.py
snf-astakos-app/astakos/im/management/commands/term-add.py
snf-astakos-app/astakos/im/management/commands/user-activation-send.py
snf-astakos-app/astakos/im/management/commands/user-add.py
snf-astakos-app/astakos/im/management/commands/user-details.py [moved from snf-astakos-app/astakos/im/management/commands/user-show.py with 92% similarity]
snf-astakos-app/astakos/im/management/commands/user-invite.py
snf-astakos-app/astakos/im/management/commands/user-list.py
snf-astakos-app/astakos/im/management/commands/user-update.py [moved from snf-astakos-app/astakos/im/management/commands/user-modify.py with 63% similarity]
snf-astakos-app/astakos/im/messages.py [new file with mode: 0644]
snf-astakos-app/astakos/im/middleware.py
snf-astakos-app/astakos/im/migrations/0001_initial.py
snf-astakos-app/astakos/im/migrations/0002_auto__add_field_astakosuser_third_party_identifier.py
snf-astakos-app/astakos/im/migrations/0003_auto__add_unique_invitation_username.py
snf-astakos-app/astakos/im/migrations/0004_auto__add_field_astakosuser_email_verified.py
snf-astakos-app/astakos/im/migrations/0005_auto__add_field_astakosuser_has_credits.py
snf-astakos-app/astakos/im/migrations/0006_auto__add_approvalterms__add_field_astakosuser_has_signed_terms__add_f.py
snf-astakos-app/astakos/im/migrations/0007_auto__chg_field_astakosuser_email_verified__chg_field_astakosuser_has_.py
snf-astakos-app/astakos/im/migrations/0007_auto__del_field_invitation_accepted__del_field_invitation_is_accepted.py
snf-astakos-app/astakos/im/migrations/0008_auto__add_emailchange.py
snf-astakos-app/astakos/im/migrations/0009_auto__add_service.py
snf-astakos-app/astakos/im/migrations/0010_auto__add_field_astakosuser_activation_sent__chg_field_service_url.py
snf-astakos-app/astakos/im/migrations/0011_set_old_activation_sent.py
snf-astakos-app/astakos/im/migrations/0012_auto__add_additionalmail.py
snf-astakos-app/astakos/im/migrations/0013_auto__del_unique_additionalmail_email.py
snf-astakos-app/astakos/im/migrations/0014_auto__add_unique_astakosuser_third_party_identifier_provider.py
snf-astakos-app/astakos/im/migrations/0015_auto__add_groupkind__add_astakosgroup__add_resourcemetadata__add_astak.py [new file with mode: 0644]
snf-astakos-app/astakos/im/migrations/0017_populate_resource_data.py [new file with mode: 0644]
snf-astakos-app/astakos/im/migrations/0018_auto__add_field_astakosgroup_homepage.py [new file with mode: 0644]
snf-astakos-app/astakos/im/migrations/0020_auto__chg_field_astakosgroup_homepage.py [new file with mode: 0644]
snf-astakos-app/astakos/im/migrations/0021_auto__add_field_astakosgroupquota_uplimit__add_field_astakosuserquota_.py [new file with mode: 0644]
snf-astakos-app/astakos/im/migrations/0022_copy_limit_to_uplimit.py [new file with mode: 0644]
snf-astakos-app/astakos/im/migrations/0024_auto__chg_field_astakosgroupquota_lim.py [new file with mode: 0644]
snf-astakos-app/astakos/im/migrations/0025_case_insensitive_emails.py [new file with mode: 0644]
snf-astakos-app/astakos/im/migrations/0026_auto__add_field_resource_desc__add_field_resource_unit.py [new file with mode: 0644]
snf-astakos-app/astakos/im/migrations/0027_auto__add_field_resource_group.py [new file with mode: 0644]
snf-astakos-app/astakos/im/migrations/0028_auto__add_field_astakosuser_disturbed_quota.py [new file with mode: 0644]
snf-astakos-app/astakos/im/migrations/0029_auto__add_field_astakosgroup_max_participants.py [new file with mode: 0644]
snf-astakos-app/astakos/im/migrations/0030_populate_resource_data.py [new file with mode: 0644]
snf-astakos-app/astakos/im/migrations/0031_populate_group_data.py [new file with mode: 0644]
snf-astakos-app/astakos/im/models.py
snf-astakos-app/astakos/im/settings.py
snf-astakos-app/astakos/im/static/im/cloudbar/cloudbar.css
snf-astakos-app/astakos/im/static/im/cloudbar/cloudbar.js
snf-astakos-app/astakos/im/static/im/css/dropkick.css
snf-astakos-app/astakos/im/static/im/css/formating.css
snf-astakos-app/astakos/im/static/im/css/forms.css
snf-astakos-app/astakos/im/static/im/css/images/ui-bg_flat_0_aaaaaa_40x100.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_55_fbf9ee_1x400.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_65_ffffff_1x400.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_75_dadada_1x400.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_75_e6e6e6_1x400.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_75_ffffff_1x400.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/css/images/ui-bg_inset-soft_95_fef1ec_1x100.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/css/images/ui-icons_222222_256x240.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/css/images/ui-icons_2e83ff_256x240.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/css/images/ui-icons_454545_256x240.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/css/images/ui-icons_888888_256x240.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/css/images/ui-icons_cd0a0a_256x240.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/css/jquery-ui-1.8.21.custom.css [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/css/modules.css
snf-astakos-app/astakos/im/static/im/css/uniform.default.css [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/arrow-down_black.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/arrow-down_blue.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/arrow-down_grey.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/arrow-up_blue.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/bandwidth-stats.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/cpu-stats.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/create-compute.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/create-network.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/create-resource-cpu.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/create-resource-disk.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/create-resource-network.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/create-resource-ram.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/create-resource-storage.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/create-resource-vm.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/create-storage.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/create.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/disk-stats.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/join.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/network-stats.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/plus-minus-hover.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/plus-minus.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/quota-related-bg.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/ram-stats.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/red-stats-vm.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/sprite.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/statistics_icons.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/stats-line.jpg [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/storage-stats.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/symbols2.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/images/vm-stats.png [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/js/common.js
snf-astakos-app/astakos/im/static/im/js/forms.js
snf-astakos-app/astakos/im/static/im/js/jquery-ui-1.8.21.custom.min.js [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/js/jquery.alerts.js [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/js/jquery.dropkick-1.0.0.js
snf-astakos-app/astakos/im/static/im/js/jquery.uniform.js [new file with mode: 0644]
snf-astakos-app/astakos/im/static/im/js/quotas.js [new file with mode: 0644]
snf-astakos-app/astakos/im/synnefo_settings.py
snf-astakos-app/astakos/im/target/local.py
snf-astakos-app/astakos/im/target/redirect.py
snf-astakos-app/astakos/im/target/shibboleth.py
snf-astakos-app/astakos/im/tasks.py [new file with mode: 0644]
snf-astakos-app/astakos/im/templates/im/account_base.html
snf-astakos-app/astakos/im/templates/im/account_notification.txt [moved from snf-astakos-app/astakos/im/templates/im/admin_notification.txt with 68% similarity]
snf-astakos-app/astakos/im/templates/im/astakosgroup_create_list.html [new file with mode: 0644]
snf-astakos-app/astakos/im/templates/im/astakosgroup_detail.html [new file with mode: 0644]
snf-astakos-app/astakos/im/templates/im/astakosgroup_form.html [new file with mode: 0644]
snf-astakos-app/astakos/im/templates/im/astakosgroup_form_summary.html [new file with mode: 0644]
snf-astakos-app/astakos/im/templates/im/astakosgroup_list.html [new file with mode: 0644]
snf-astakos-app/astakos/im/templates/im/astakosuserquota_list.html [new file with mode: 0644]
snf-astakos-app/astakos/im/templates/im/base.html
snf-astakos-app/astakos/im/templates/im/billing.html [new file with mode: 0644]
snf-astakos-app/astakos/im/templates/im/feedback.html
snf-astakos-app/astakos/im/templates/im/footer.html
snf-astakos-app/astakos/im/templates/im/group_creation_notification.txt [new file with mode: 0644]
snf-astakos-app/astakos/im/templates/im/invitations.html
snf-astakos-app/astakos/im/templates/im/profile.html
snf-astakos-app/astakos/im/templates/im/projects/list_types.html [new file with mode: 0644]
snf-astakos-app/astakos/im/templates/im/register.html
snf-astakos-app/astakos/im/templates/im/resource_list.html [new file with mode: 0644]
snf-astakos-app/astakos/im/templates/im/signup.html
snf-astakos-app/astakos/im/templates/im/signup_complete.html
snf-astakos-app/astakos/im/templates/im/third_party_registration.html
snf-astakos-app/astakos/im/templates/im/timeline.html [new file with mode: 0644]
snf-astakos-app/astakos/im/templates/registration/email_change_form.html
snf-astakos-app/astakos/im/templates/registration/logged_out.html
snf-astakos-app/astakos/im/templatetags/astakos_tags.py
snf-astakos-app/astakos/im/templatetags/filters.py
snf-astakos-app/astakos/im/urls.py
snf-astakos-app/astakos/im/util.py
snf-astakos-app/astakos/im/views.py
snf-astakos-app/astakos/im/widgets.py
snf-astakos-app/astakos/urls.py
snf-astakos-app/conf/20-snf-astakos-app-settings.conf
snf-astakos-app/distribute-0.6.10-py2.6.egg [new file with mode: 0644]
snf-astakos-app/distribute-0.6.10.tar.gz [new file with mode: 0644]
snf-astakos-app/distribute_setup.py
snf-astakos-app/setup.py

index 7648fd5..a9b3bbb 100644 (file)
@@ -6,3 +6,5 @@ docs/build
 .project
 .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
diff --git a/snf-astakos-app/.settings/org.eclipse.core.resources.prefs b/snf-astakos-app/.settings/org.eclipse.core.resources.prefs
new file mode 100644 (file)
index 0000000..d88f102
--- /dev/null
@@ -0,0 +1,6 @@
+#Mon Nov 05 16:04:44 EET 2012
+eclipse.preferences.version=1
+encoding//astakos/im/migrations/0010_auto__add_field_astakosuser_activation_sent__chg_field_service_url.py=utf-8
+encoding//astakos/im/migrations/0011_set_old_activation_sent.py=utf-8
+encoding//astakos/im/migrations/0017_populate_resource_data.py=utf-8
+encoding//astakos/im/migrations/0030_populate_resource_data.py=utf-8
index 7f6e33b..cc11b1c 100644 (file)
@@ -1,5 +1,10 @@
 Changelog
 ---------
+next
+^^^^
+
+- Setting ASTAKOS_DEFAULT_ADMIN_EMAIL has been deprecated. Use ADMINS django setting instead.
+- Setting ASTAKOS_DEFAULT_FROM_EMAIL has been deprecated. Use SERVER_EMAIL django setting instead.
 
 v0.7.5
 ^^^^^^
index 0d792d4..eea3a07 100644 (file)
@@ -85,10 +85,43 @@ ASTAKOS_INVITATION_EMAIL_SUBJECT            'Invitation to %s alpha2 testing' %
 ASTAKOS_GREETING_EMAIL_SUBJECT              'Welcome to %s alpha2 testing' % SITENAME                                       Welcome email subject
 ASTAKOS_FEEDBACK_EMAIL_SUBJECT              'Feedback from %s alpha2 testing' % SITENAME                                    Feedback email subject
 ASTAKOS_VERIFICATION_EMAIL_SUBJECT          '%s alpha2 testing account activation is needed' % SITENAME                     Account activation email subject
-ASTAKOS_ADMIN_NOTIFICATION_EMAIL_SUBJECT    '%s alpha2 testing account created (%%(user)s)' % SITENAME                      Account creation admin notification email subject
+ASTAKOS_ACCOUNT_CREATION_SUBJECT            '%s alpha2 testing account created (%%(user)s)' % SITENAME                      Account creation email subject
+ASTAKOS_GROUP_CREATION_SUBJECT              '%s alpha2 testing group created (%%(group)s)' % SITENAME                       Group creation email subject
 ASTAKOS_HELPDESK_NOTIFICATION_EMAIL_SUBJECT '%s alpha2 testing account activated (%%(user)s)' % SITENAME                    Account activation helpdesk notification email subject
 ASTAKOS_EMAIL_CHANGE_EMAIL_SUBJECT          'Email change on %s alpha2 testing' % SITENAME                                  Email change subject               
 ASTAKOS_PASSWORD_RESET_EMAIL_SUBJECT        'Password reset on %s alpha2 testing' % SITENAME                                Password change email subject
+
+ASTAKOS_QUOTA_HOLDER_URL                    ''                                                                              The quota holder URI
+                                                                                                                            e.g. ``http://localhost:8080/api/quotaholder/v``
+ASTAKOS_SERVICES                            {'cyclades': {'resources': [{'desc': 'Number of virtual machines',              Default cloud service information
+                                            'group': 'compute',
+                                            'name': 'vm',
+                                            'uplimit': 2},
+                                            {'desc': 'Virtual machine disk size',
+                                            'group': 'compute',
+                                            'name': 'diskspace',
+                                            'unit': 'GB',
+                                            'uplimit': 5},
+                                            {'desc': 'Number of virtual machine processors',
+                                            'group': 'compute',
+                                            'name': 'cpu',
+                                            'uplimit': 1},
+                                            {'desc': 'Virtual machines',
+                                            'group': 'compute',
+                                            'name': 'ram',
+                                            'unit': 'MB',
+                                            'uplimit': 1024}],
+                                            'url': 'https://node1.example.com/ui/'},
+                                            'pithos+': {'resources': [{'desc': 'Pithos account diskspace',
+                                            'group': 'storage',
+                                            'name': 'diskspace',
+                                            'unit': 'bytes',
+                                            'uplimit': 5368709120}],
+                                            'url': 'https://node2.example.com/ui/'}}                                                                               
+ASTAKOS_AQUARIUM_URL                        ''                                                                              The billing (aquarium) URI
+                                                                                                                            e.g. ``http://localhost:8888/user``
+ASTAKOS_PAGINATE_BY                         10                                                                              Number of object to be displayed per page
+
 ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN          True                                                                            Enforce token renewal on password change/reset. If set to False, user can optionally decide
                                                                                                                             whether to renew the token or not.
 ASTAKOS_ENABLE_LOCAL_ACCOUNT_MIGRATION      True                                                                            Permit local account migration to third party account
@@ -117,11 +150,4 @@ showuser         Show user info
 
 To update user credibility from the billing system (Aquarium), enable the queue, install snf-pithos-tools and use ``pithos-dispatcher``::
 
-    pithos-dispatcher --exchange=aquarium --callback=astakos.im.queue.listener.on_creditevent
-
-Load groups:
-------------
-
-To set the initial user groups load the followind fixture:
-
-    snf-manage loaddata groups
+    pithos-dispatcher --exchange=aquarium --callback=astakos.im.endpoints.aquarium.consumer.on_creditevent
index b7d68db..2231d24 100644 (file)
@@ -1,20 +1,20 @@
 # Copyright (c) Django Software Foundation and individual contributors.
 # All rights reserved.
-# 
+#
 # Redistribution and use in source and binary forms, with or without modification,
 # are permitted provided that the following conditions are met:
-# 
-#     1. Redistributions of source code must retain the above copyright notice, 
+#
+#     1. Redistributions of source code must retain the above copyright notice,
 #        this list of conditions and the following disclaimer.
-#     
-#     2. Redistributions in binary form must reproduce the above copyright 
+#
+#     2. Redistributions in binary form must reproduce the above copyright
 #        notice, this list of conditions and the following disclaimer in the
 #        documentation and/or other materials provided with the distribution.
-# 
+#
 #     3. Neither the name of Django nor the names of its contributors may be used
 #        to endorse or promote products derived from this software without
 #        specific prior written permission.
-# 
+#
 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@@ -28,6 +28,7 @@
 
 VERSION = (0, 3, 0, 'alpha', 0)
 
+
 def get_version():
     version = '%s.%s' % (VERSION[0], VERSION[1])
     if VERSION[2]:
index cd70769..108d4f9 100644 (file)
 
 from django.utils.importlib import import_module
 from django.core.exceptions import ImproperlyConfigured
-from django.core.mail import send_mail
-from django.template.loader import render_to_string
-from django.contrib import messages
-from django.core.urlresolvers import reverse
 from django.utils.translation import ugettext as _
-from django.db import transaction
 
-from urlparse import urljoin
-
-from astakos.im.models import AstakosUser, Invitation
-from astakos.im.forms import *
+from astakos.im.models import AstakosUser
 from astakos.im.util import get_invitation
-from astakos.im.functions import send_verification, send_activation, \
-    send_admin_notification, activate, SendMailError
-from astakos.im.settings import INVITATIONS_ENABLED, DEFAULT_CONTACT_EMAIL, \
-    DEFAULT_FROM_EMAIL, MODERATION_ENABLED, SITENAME, DEFAULT_ADMIN_EMAIL, RE_USER_EMAIL_PATTERNS
+from astakos.im.functions import (
+    send_activation, send_account_creation_notification, activate
+)
+from astakos.im.settings import (
+    INVITATIONS_ENABLED, MODERATION_ENABLED, RE_USER_EMAIL_PATTERNS
+)
+
+import astakos.im.messages as astakos_messages
 
-import socket
 import logging
 import re
 
 logger = logging.getLogger(__name__)
 
+
 def get_backend(request):
     """
     Returns an instance of an activation backend,
@@ -68,31 +64,36 @@ def get_backend(request):
     """
     module = 'astakos.im.activation_backends'
     prefix = 'Invitations' if INVITATIONS_ENABLED else 'Simple'
-    backend_class_name = '%sBackend' %prefix
+    backend_class_name = '%sBackend' % prefix
     try:
         mod = import_module(module)
     except ImportError, e:
-        raise ImproperlyConfigured('Error loading activation backend %s: "%s"' % (module, e))
+        raise ImproperlyConfigured(
+            'Error loading activation backend %s: "%s"' % (module, e))
     try:
         backend_class = getattr(mod, backend_class_name)
     except AttributeError:
-        raise ImproperlyConfigured('Module "%s" does not define a activation backend named "%s"' % (module, attr))
+        raise ImproperlyConfigured('Module "%s" does not define a activation backend named "%s"' % (module, backend_class_name))
     return backend_class(request)
 
+
 class ActivationBackend(object):
+    def __init__(self, request):
+        self.request = request
+
     def _is_preaccepted(self, user):
         # return True if user email matches specific patterns
         for pattern in RE_USER_EMAIL_PATTERNS:
             if re.match(pattern, user.email):
                 return True
         return False
-    
+
     def get_signup_form(self, provider='local', instance=None):
         """
         Returns a form instance of the relevant class
         """
         main = provider.capitalize() if provider == 'local' else 'ThirdParty'
-        suffix  = 'UserCreationForm'
+        suffix = 'UserCreationForm'
         formclass = '%s%s' % (main, suffix)
         request = self.request
         initial_data = None
@@ -126,12 +127,16 @@ class ActivationBackend(object):
                     send_activation(user, activation_template_name)
                     return VerificationSent()
             else:
-                send_admin_notification(user, admin_email_template_name)
+                send_account_creation_notification(
+                    template_name=admin_email_template_name,
+                    dictionary={'user': user.__dict__, 'group_creation': True}
+                )
                 return NotificationSent()
         except BaseException, e:
             logger.exception(e)
             raise e
 
+
 class InvitationsBackend(ActivationBackend):
     """
     A activation backend which implements the following workflow: a user
@@ -140,14 +145,11 @@ class InvitationsBackend(ActivationBackend):
     account is created and the user is going to receive an email as soon as an
     administrator activates his/her account.
     """
-    def __init__(self, request):
-        self.request = request
-        super(InvitationsBackend, self).__init__()
 
     def get_signup_form(self, provider='local', instance=None):
         """
         Returns a form instance of the relevant class
-        
+
         raises Invitation.DoesNotExist and ValueError if invitation is consumed
         or invitation username is reserved.
         """
@@ -156,7 +158,7 @@ class InvitationsBackend(ActivationBackend):
         initial_data = self.get_signup_initial_data(provider)
         prefix = 'Invited' if invitation else ''
         main = provider.capitalize()
-        suffix  = 'UserCreationForm'
+        suffix = 'UserCreationForm'
         formclass = '%s%s%s' % (prefix, main, suffix)
         return globals()[formclass](initial_data, instance=instance, request=self.request)
 
@@ -173,12 +175,12 @@ class InvitationsBackend(ActivationBackend):
             if invitation:
                 # create a tmp user with the invitation realname
                 # to extract first and last name
-                u = AstakosUser(realname = invitation.realname)
-                initial_data = {'email':invitation.username,
-                                'inviter':invitation.inviter.realname,
-                                'first_name':u.first_name,
-                                'last_name':u.last_name,
-                                'provider':provider}
+                u = AstakosUser(realname=invitation.realname)
+                initial_data = {'email': invitation.username,
+                                'inviter': invitation.inviter.realname,
+                                'first_name': u.first_name,
+                                'last_name': u.last_name,
+                                'provider': provider}
         else:
             if provider == request.POST.get('provider', ''):
                 initial_data = request.POST
@@ -199,16 +201,13 @@ class InvitationsBackend(ActivationBackend):
             return True
         return False
 
+
 class SimpleBackend(ActivationBackend):
     """
     A activation backend which implements the following workflow: a user
     supplies the necessary registation information, an incative user account is
     created and receives an email in order to activate his/her account.
     """
-    def __init__(self, request):
-        self.request = request
-        super(SimpleBackend, self).__init__()
-    
     def _is_preaccepted(self, user):
         if super(SimpleBackend, self)._is_preaccepted(user):
             return True
@@ -216,23 +215,24 @@ class SimpleBackend(ActivationBackend):
             return False
         return True
 
+
 class ActivationResult(object):
     def __init__(self, message):
         self.message = message
 
+
 class VerificationSent(ActivationResult):
     def __init__(self):
-        message = _('Verification sent.')
+        message = _(astakos_messages.VERIFICATION_SENT)
         super(VerificationSent, self).__init__(message)
 
 class NotificationSent(ActivationResult):
     def __init__(self):
-        message = _('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.')
+        message = _(astakos_messages.NOTIFICATION_SENT)
         super(NotificationSent, self).__init__(message)
 
+
 class RegistationCompleted(ActivationResult):
     def __init__(self):
-        message = _('Registration completed. You can now login.')
+        message = _(astakos_messages.REGISTRATION_COMPLETED)
         super(RegistationCompleted, self).__init__(message)
\ No newline at end of file
index 6bb6daf..14ea26c 100644 (file)
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
+from functools import wraps
+from traceback import format_exc
+from urllib import quote, unquote
+
 from django.http import HttpResponse
 from django.utils import simplejson as json
+from django.conf import settings
+from django.core.urlresolvers import reverse
+
+from astakos.im.models import AstakosUser, GroupKind, Service, Resource
+from astakos.im.api.faults import Fault, ItemNotFound, InternalServerError, BadRequest
+from astakos.im.settings import INVITATIONS_ENABLED, COOKIE_NAME, EMAILCHANGE_ENABLED
 
-from astakos.im.models import AstakosUser
-from astakos.im.api.faults import ItemNotFound
+import logging
+logger = logging.getLogger(__name__)
 
 format = ('%a, %d %b %Y %H:%M:%S GMT')
 
+absolute = lambda request, url: request.build_absolute_uri(url)
+
+
+def render_fault(request, fault):
+    if isinstance(fault, InternalServerError) and settings.DEBUG:
+        fault.details = format_exc(fault)
+
+    request.serialization = 'text'
+    data = fault.message + '\n'
+    if fault.details:
+        data += '\n' + fault.details
+    response = HttpResponse(data, status=fault.code)
+    response['Content-Length'] = len(response.content)
+    return response
+
+
+def api_method(http_method=None):
+    """Decorator function for views that implement an API method."""
+    def decorator(func):
+        @wraps(func)
+        def wrapper(request, *args, **kwargs):
+            try:
+                if http_method and request.method != http_method:
+                    raise BadRequest('Method not allowed.')
+                response = func(request, *args, **kwargs)
+                return response
+            except Fault, fault:
+                return render_fault(request, fault)
+            except BaseException, e:
+                logger.exception('Unexpected error: %s' % e)
+                fault = InternalServerError('Unexpected error')
+                return render_fault(request, fault)
+        return wrapper
+    return decorator
+
+
 def _get_user_by_username(user_id):
     try:
-        user = AstakosUser.objects.get(username = user_id)
-    except AstakosUser.DoesNotExist, e:
+        user = AstakosUser.objects.get(username=user_id)
+    except AstakosUser.DoesNotExist:
         raise ItemNotFound('Invalid userid')
     else:
         response = HttpResponse()
-        response.status=200
-        user_info = {'id':user.id,
-                     'username':user.username,
-                     'email':[user.email],
-                     'name':user.realname,
-                     'auth_token_created':user.auth_token_created.strftime(format),
-                     'auth_token_expires':user.auth_token_expires.strftime(format),
-                     'has_credits':user.has_credits,
-                     'enabled':user.is_active,
-                     'groups':[g.name for g in user.groups.all()]}
+        response.status = 200
+        user_info = {'id': user.id,
+                     'username': user.username,
+                     'email': [user.email],
+                     'name': user.realname,
+                     'auth_token_created': user.auth_token_created.strftime(format),
+                     'auth_token_expires': user.auth_token_expires.strftime(format),
+                     'has_credits': user.has_credits,
+                     'enabled': user.is_active,
+                     'groups': [g.name for g in user.groups.all()]}
         response.content = json.dumps(user_info)
         response['Content-Type'] = 'application/json; charset=UTF-8'
         response['Content-Length'] = len(response.content)
         return response
 
+
 def _get_user_by_email(email):
     if not email:
         raise BadRequest('Email missing')
     try:
-        user = AstakosUser.objects.get(email = email)
-    except AstakosUser.DoesNotExist, e:
+        user = AstakosUser.objects.get(email=email)
+    except AstakosUser.DoesNotExist:
         raise ItemNotFound('Invalid email')
-    
+
     if not user.is_active:
         raise ItemNotFound('Inactive user')
     else:
         response = HttpResponse()
-        response.status=200
-        user_info = {'id':user.id,
-                     'username':user.username,
-                     'email':[user.email],
-                     'enabled':user.is_active,
-                     'name':user.realname,
-                     'auth_token_created':user.auth_token_created.strftime(format),
-                     'auth_token_expires':user.auth_token_expires.strftime(format),
-                     'has_credits':user.has_credits,
-                     'groups':[g.name for g in user.groups.all()],
-                     'user_permissions':[p.codename for p in user.user_permissions.all()]}
+        response.status = 200
+        user_info = {'id': user.id,
+                     'username': user.username,
+                     'email': [user.email],
+                     'enabled': user.is_active,
+                     'name': user.realname,
+                     'auth_token_created': user.auth_token_created.strftime(format),
+                     'auth_token_expires': user.auth_token_expires.strftime(format),
+                     'has_credits': user.has_credits,
+                     'groups': [g.name for g in user.groups.all()],
+                     'user_permissions': [p.codename for p in user.user_permissions.all()]}
         response.content = json.dumps(user_info)
         response['Content-Type'] = 'application/json; charset=UTF-8'
         response['Content-Length'] = len(response.content)
-        return response
\ No newline at end of file
+        return response
+
+
+@api_method(http_method='GET')
+def get_services(request):
+    callback = request.GET.get('callback', None)
+    services = Service.objects.all()
+    data = tuple({'id': s.pk, 'name': s.name, 'url': s.url, 'icon':
+                 s.icon} for s in services)
+    data = json.dumps(data)
+    mimetype = 'application/json'
+
+    if callback:
+        mimetype = 'application/javascript'
+        data = '%s(%s)' % (callback, data)
+
+    return HttpResponse(content=data, mimetype=mimetype)
+
+
+@api_method()
+def get_menu(request, with_extra_links=False, with_signout=True):
+    user = request.user
+    index_url = reverse('index')
+    l = [{'url': absolute(request, index_url), 'name': "Sign in"}]
+    if user.is_authenticated():
+        l = []
+        append = l.append
+        item = MenuItem
+        item.current_path = absolute(request, request.path)
+        append(item(
+               url=absolute(request, reverse('index')),
+               name=user.email))
+        append(item(url=absolute(request, reverse('edit_profile')),
+               name="My account"))
+        if with_extra_links:
+            if user.has_usable_password() and user.provider in ('local', ''):
+                append(item(
+                       url=absolute(request, reverse('password_change')),
+                       name="Change password"))
+            if EMAILCHANGE_ENABLED:
+                append(item(
+                       url=absolute(request, reverse('email_change')),
+                       name="Change email"))
+            if INVITATIONS_ENABLED:
+                append(item(
+                       url=absolute(request, reverse('invite')),
+                       name="Invitations"))
+            
+            append(item(
+                   url=absolute(request, reverse('group_list')),
+                   name="Groups",
+                   submenu=(item(
+                            url=absolute(request,
+                                         reverse('group_list')),
+                            name="Overview"),
+                            item(
+                                url=absolute(request,
+                                             reverse('group_create_list')),
+                                name="Create"),
+                            item(
+                                url=absolute(request,
+                                             reverse('group_search')),
+                                name="Join"),)))
+            append(item(
+                   url=absolute(request, reverse('resource_list')),
+                   name="Report"))
+            append(item(
+                   url=absolute(request, reverse('feedback')),
+                   name="Feedback"))
+            append(item(
+                   url=absolute(request, reverse('billing')),
+                   name="Billing"))
+            append(item(
+                   url=absolute(request, reverse('timeline')),
+                   name="Timeline"))
+        if with_signout:
+            append(item(
+                   url=absolute(request, reverse('logout')),
+                   name="Sign out"))
+
+    callback = request.GET.get('callback', None)
+    data = json.dumps(tuple(l))
+    mimetype = 'application/json'
+
+    if callback:
+        mimetype = 'application/javascript'
+        data = '%s(%s)' % (callback, data)
+
+    return HttpResponse(content=data, mimetype=mimetype)
+
+
+class MenuItem(dict):
+    current_path = ''
+
+    def __init__(self, *args, **kwargs):
+        super(MenuItem, self).__init__(*args, **kwargs)
+        if kwargs.get('url') or kwargs.get('submenu'):
+            self.__set_is_active__()
+
+    def __setitem__(self, key, value):
+        super(MenuItem, self).__setitem__(key, value)
+        if key in ('url', 'submenu'):
+            self.__set_is_active__()
+
+    def __set_is_active__(self):
+        if self.get('is_active'):
+            return
+        if self.current_path == self.get('url'):
+            self.__setitem__('is_active', True)
+        else:
+            submenu = self.get('submenu', ())
+            current = (i for i in submenu if i.get('url') == self.current_path)
+            try:
+                current_node = current.next()
+                if not current_node.get('is_active'):
+                    current_node.__setitem__('is_active', True)
+                self.__setitem__('is_active', True)
+            except StopIteration:
+                return
+
+    def __setattribute__(self, name, value):
+        super(MenuItem, self).__setattribute__(name, value)
+        if name == 'current_path':
+            self.__set_is_active__()
index 3d02874..47196fd 100644 (file)
 # or implied, of GRNET S.A.
 
 import logging
-import urllib
 
 from functools import wraps
-from traceback import format_exc
 from time import time, mktime
-from urllib import quote
-from urlparse import urlparse
-from collections import defaultdict
 
-from django.conf import settings
 from django.http import HttpResponse
 from django.utils import simplejson as json
-from django.core.urlresolvers import reverse
 
-from astakos.im.api.faults import *
-from astakos.im.models import AstakosUser, Service
-from astakos.im.settings import INVITATIONS_ENABLED, EMAILCHANGE_ENABLED
+from astakos.im.api.faults import (
+    Fault, Unauthorized, InternalServerError, BadRequest,
+    Forbidden
+)
+from astakos.im.api import render_fault, _get_user_by_email, _get_user_by_username
+from astakos.im.models import AstakosUser
 from astakos.im.util import epoch
-from astakos.im.api import _get_user_by_email, _get_user_by_username
 
 logger = logging.getLogger(__name__)
 format = ('%a, %d %b %Y %H:%M:%S GMT')
 
-def render_fault(request, fault):
-    if isinstance(fault, InternalServerError) and settings.DEBUG:
-        fault.details = format_exc(fault)
-
-    request.serialization = 'text'
-    data = fault.message + '\n'
-    if fault.details:
-        data += '\n' + fault.details
-    response = HttpResponse(data, status=fault.code)
-    response['Content-Length'] = len(response.content)
-    return response
 
 def api_method(http_method=None, token_required=False, perms=None):
     """Decorator function for views that implement an API method."""
@@ -100,6 +84,7 @@ def api_method(http_method=None, token_required=False, perms=None):
         return wrapper
     return decorator
 
+
 @api_method(http_method='GET', token_required=True)
 def authenticate_old(request, user=None):
     # Normal Response Codes: 204
@@ -117,24 +102,25 @@ def authenticate_old(request, user=None):
     if (time() - mktime(user.auth_token_expires.timetuple())) > 0:
         raise Unauthorized('Authentication expired')
 
-    if not user.signed_terms():
+    if not user.signed_terms:
         raise Unauthorized('Pending approval terms')
 
     response = HttpResponse()
-    response.status=204
-    user_info = {'username':user.username,
-                 'uniq':user.email,
-                 'auth_token':user.auth_token,
-                 'auth_token_created':user.auth_token_created.isoformat(),
-                 'auth_token_expires':user.auth_token_expires.isoformat(),
-                 'has_credits':user.has_credits,
-                 'has_signed_terms':user.signed_terms(),
-                 'groups':[g.name for g in user.groups.all()]}
+    response.status = 204
+    user_info = {'username': user.username,
+                 'uniq': user.email,
+                 'auth_token': user.auth_token,
+                 'auth_token_created': user.auth_token_created.isoformat(),
+                 'auth_token_expires': user.auth_token_expires.isoformat(),
+                 'has_credits': user.has_credits,
+                 'has_signed_terms': user.signed_terms,
+                 'groups': [g.name for g in user.groups.all()]}
     response.content = json.dumps(user_info)
     response['Content-Type'] = 'application/json; charset=UTF-8'
     response['Content-Length'] = len(response.content)
     return response
 
+
 @api_method(http_method='GET', token_required=True)
 def authenticate(request, user=None):
     # Normal Response Codes: 204
@@ -152,76 +138,25 @@ def authenticate(request, user=None):
     if (time() - mktime(user.auth_token_expires.timetuple())) > 0:
         raise Unauthorized('Authentication expired')
 
-    if not user.signed_terms():
+    if not user.signed_terms:
         raise Unauthorized('Pending approval terms')
 
     response = HttpResponse()
-    response.status=204
-    user_info = {'userid':user.username,
-                 'email':[user.email],
-                 'name':user.realname,
-                 'auth_token':user.auth_token,
-                 'auth_token_created':epoch(user.auth_token_created),
-                 'auth_token_expires':epoch(user.auth_token_expires),
-                 'has_credits':user.has_credits,
-                 'is_active':user.is_active,
-                 'groups':[g.name for g in user.groups.all()]}
+    response.status = 204
+    user_info = {'userid': user.username,
+                 'email': [user.email],
+                 'name': user.realname,
+                 'auth_token': user.auth_token,
+                 'auth_token_created': epoch(user.auth_token_created),
+                 'auth_token_expires': epoch(user.auth_token_expires),
+                 'has_credits': user.has_credits,
+                 'is_active': user.is_active,
+                 'groups': [g.name for g in user.groups.all()]}
     response.content = json.dumps(user_info)
     response['Content-Type'] = 'application/json; charset=UTF-8'
     response['Content-Length'] = len(response.content)
     return response
 
-@api_method(http_method='GET')
-def get_services(request):
-    callback = request.GET.get('callback', None)
-    services = Service.objects.all()
-    data = tuple({'id':s.pk, 'name':s.name, 'url':s.url, 'icon':s.icon} for s in services)
-    data = json.dumps(data)
-    mimetype = 'application/json'
-
-    if callback:
-        mimetype = 'application/javascript'
-        data = '%s(%s)' % (callback, data)
-
-    return HttpResponse(content=data, mimetype=mimetype)
-
-@api_method()
-def get_menu(request, with_extra_links=False, with_signout=True):
-    index_url = reverse('index')
-    absolute = lambda (url): request.build_absolute_uri(url)
-    l = [{ 'url': absolute(index_url), 'name': "Sign in"}]
-    user = request.user
-    if user.is_authenticated():
-        l = []
-        l.append({ 'url': absolute(reverse('astakos.im.views.index')),
-                  'name': user.email})
-        l.append({ 'url': absolute(reverse('astakos.im.views.edit_profile')),
-                  'name': "My account" })
-        if with_extra_links:
-            if user.has_usable_password() and user.provider == 'local':
-                l.append({ 'url': absolute(reverse('password_change')),
-                          'name': "Change password" })
-            if EMAILCHANGE_ENABLED:
-                l.append({'url':absolute(reverse('email_change')),
-                          'name': "Change email"})
-            if INVITATIONS_ENABLED:
-                l.append({ 'url': absolute(reverse('astakos.im.views.invite')),
-                          'name': "Invitations" })
-            l.append({ 'url': absolute(reverse('astakos.im.views.feedback')),
-                      'name': "Feedback" })
-        if with_signout:
-            l.append({ 'url': absolute(reverse('astakos.im.views.logout')),
-                      'name': "Sign out"})
-
-    callback = request.GET.get('callback', None)
-    data = json.dumps(tuple(l))
-    mimetype = 'application/json'
-
-    if callback:
-        mimetype = 'application/javascript'
-        data = '%s(%s)' % (callback, data)
-
-    return HttpResponse(content=data, mimetype=mimetype)
 
 @api_method(http_method='GET', token_required=True, perms=['im.can_access_userinfo'])
 def get_user_by_email(request, user=None):
@@ -234,6 +169,7 @@ def get_user_by_email(request, user=None):
     email = request.GET.get('name')
     return _get_user_by_email(email)
 
+
 @api_method(http_method='GET', token_required=True, perms=['im.can_access_userinfo'])
 def get_user_by_username(request, user_id, user=None):
     # Normal Response Codes: 200
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
-import logging
-import json
+from astakos.im.api.backends.lib.django import DjangoBackend
 
-from astakos.im.functions import set_user_credibility
 
-logger = logging.getLogger(__name__)
-
-def on_creditevent(msg):
-    try:
-        email = msg.get('userid')
-        has_credits = True if msg.get('status') == 'on' else False
-        set_user_credibility(email, has_credits)
-    except Exception, e:
-        logger.exception(e)
\ No newline at end of file
+def get_backend():
+    return DjangoBackend()
diff --git a/snf-astakos-app/astakos/im/api/backends/base.py b/snf-astakos-app/astakos/im/api/backends/base.py
new file mode 100644 (file)
index 0000000..db1514e
--- /dev/null
@@ -0,0 +1,23 @@
+import astakos.im.api.backends.errors
+
+(SUCCESS, FAILURE) = range(2)
+
+class BaseBackend(object):
+    #TODO filled
+    pass
+
+class SuccessResult():
+    def __init__(self, data):
+        self.data = data
+    
+    @property
+    def is_success(self):
+        return True
+
+class FailureResult():
+    def __init__(self, reason):
+        self.reason = reason
+    
+    @property
+    def is_success(self):
+        return False
diff --git a/snf-astakos-app/astakos/im/api/backends/errors.py b/snf-astakos-app/astakos/im/api/backends/errors.py
new file mode 100644 (file)
index 0000000..042aac2
--- /dev/null
@@ -0,0 +1,53 @@
+# Copyright 2011-2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+class ItemNotExists(ValueError):
+    def __init__(self, type, **kwargs):
+        fields = " and ".join('%s=%s' % (k,v) for k,v in kwargs.iteritems())
+        msg = "%(type)s with %(fields)s does not exist."
+        super(ItemNotExists, self).__init__(msg % locals())
+
+class ItemExists(ValueError):
+    def __init__(self, type, **kwargs):
+        fields = " and ".join('%s=%s' % (k,v) for k,v in kwargs.iteritems())
+        msg = "%(type)s with %(fields)s already exists."
+        super(ItemExists, self).__init__(msg % locals())
+
+class MultipleItemsExist(ValueError):
+    def __init__(self, type, **kwargs):
+        fields = " and ".join('%s=%s' % (k,v) for k,v in kwargs.iteritems())
+        msg = "There are mulptiple %(type)s with %(fields)s."
+        super(MultipleItemsExist, self).__init__(msg % locals())
+
+class MissingIdentifier(IOError):
+    pass
\ No newline at end of file
diff --git a/snf-astakos-app/astakos/im/api/backends/lib/__init__.py b/snf-astakos-app/astakos/im/api/backends/lib/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
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
new file mode 100644 (file)
index 0000000..38c3629
--- /dev/null
@@ -0,0 +1,307 @@
+# Copyright 2011-2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from django.db import IntegrityError, transaction
+from django.core.exceptions import ObjectDoesNotExist
+
+from functools import wraps
+from smtplib import SMTPException
+
+from astakos.im.models import (
+    AstakosUser, AstakosGroup, GroupKind, Resource, Service, RESOURCE_SEPARATOR
+)
+from astakos.im.api.backends.base import BaseBackend, SuccessResult, FailureResult
+from astakos.im.api.backends.errors import (
+    ItemNotExists, ItemExists, MissingIdentifier, MultipleItemsExist
+)
+from astakos.im.util import reserved_email, model_to_dict
+from astakos.im.endpoints.qh import get_quota
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+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))
+        try:
+            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
+
+
+class DjangoBackend(BaseBackend):
+    def _lookup_object(self, 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 _lookup_user(self, id):
+        """
+        Returns an AstakosUser having this id.
+        """
+        if not isinstance(id, int):
+            raise TypeError('User id should be of type int')
+        return self._lookup_object(AstakosUser, id=id)
+
+    def _lookup_service(self, id):
+        """
+        Returns an Service having this id.
+        """
+        if not isinstance(id, int):
+            raise TypeError('Service id should be of type int')
+        return self._lookup_object(Service, id=id)
+
+    def _list(self, model, filter=()):
+        q = model.objects.all()
+        if filter:
+            q = q.filter(id__in=filter)
+        return map(lambda o: model_to_dict(o, exclude=[]), q)
+
+    def _create_object(self, model, **kwargs):
+        o = model.objects.create(**kwargs)
+        o.save()
+        return o
+
+    def _update_object(self, model, id, save=True, **kwargs):
+        o = self._lookup_object(model, id=id)
+        if kwargs:
+            o.__dict__.update(kwargs)
+        if save:
+            o.save()
+        return o
+
+    @safe
+    def update_user(self, user_id, renew_token=False, **kwargs):
+        user = self._update_object(AstakosUser, user_id, save=False, **kwargs)
+        if renew_token:
+            user.renew_token()
+        if kwargs or renew_token:
+            user.save()
+
+    @safe
+    def create_user(self, **kwargs):
+        policies = kwargs.pop('policies', ())
+        permissions = kwargs.pop('permissions', ())
+        groups = kwargs.pop('groups', ())
+        password = kwargs.pop('password', None)
+
+        u = self._create_object(AstakosUser, **kwargs)
+
+        if password:
+            u.set_password(password)
+        u.permissions = permissions
+        u.policies = policies
+        u.extended_groups = groups
+        return self._list(AstakosUser, filter=(u.id,))
+
+    @safe
+    def add_policies(self, user_id, update=False, policies=()):
+        user = self._lookup_user(user_id)
+        rejected = []
+        append = rejected.append
+        for p in policies:
+            service = p.get('service')
+            resource = p.get('resource')
+            uplimit = p.get('uplimit')
+            try:
+                user.add_policy(service, resource, uplimit, update)
+            except (ObjectDoesNotExist, IntegrityError), e:
+                append((service, resource, e))
+        return rejected
+    
+    @safe
+    def remove_policies(self, user_id, policies=()):
+        user = self._lookup_user(user_id)
+        if not user:
+            return user_id
+        rejected = []
+        append = rejected.append
+        for p in policies:
+            service = p.get('service')
+            resource = p.get('resource')
+            try:
+                user.delete_policy(service, resource)
+            except ObjectDoesNotExist, e:
+                append((service, resource, e))
+        return rejected
+    @safe
+    def add_permissions(self, user_id, permissions=()):
+        user = self._lookup_user(user_id)
+        rejected = []
+        append = rejected.append
+        for p in permissions:
+            try:
+                user.add_permission(p)
+            except IntegrityError, e:
+                append((p, e))
+        return rejected
+    
+    @safe
+    def remove_permissions(self, user_id, permissions=()):
+        user = self._lookup_user(user_id)
+        rejected = []
+        append = rejected.append
+        for p in permissions:
+            try:
+                user.remove_permission(p)
+            except (ObjectDoesNotExist, IntegrityError), e:
+                append((p, e))
+        return rejected
+    
+    @safe
+    def invite_users(self, senderid, recipients=()):
+        user = self._lookup_user(senderid)
+        rejected = []
+        append = rejected.append
+        for r in recipients:
+            email = r.get('email')
+            realname = r.get('realname')
+            try:
+                user.invite(email, realname)
+            except (IntegrityError, SMTPException), e:
+                append((email, e))
+        return rejected
+    
+    @safe
+    def list_users(self, filter=()):
+        return self._list(AstakosUser, filter=filter)
+
+    @safe
+    def get_resource_usage(self, user_id):
+        user = self._lookup_user(user_id)
+        c, data = get_quota((user,))
+        resources = []
+        append = resources.append
+        for t in data:
+            t = (i if i else 0 for i in t)
+            (entity, name, quantity, capacity, importLimit, exportLimit,
+             imported, exported, returned, released, flags) = t
+            service, sep, resource = name.partition(RESOURCE_SEPARATOR)
+            resource = Resource.objects.select_related().get(
+                service__name=service, name=resource)
+            d = dict(name=name,
+                     description=resource.desc,
+                     unit=resource.unit or '',
+                     maxValue=quantity + capacity,
+                     currValue=quantity + imported - released - exported + returned)
+            append(d)
+        return resources
+
+    @safe
+    def list_resources(self, filter=()):
+        return self._list(Resource, filter=filter)
+
+    @safe
+    def create_service(self, **kwargs):
+        resources = kwargs.pop('resources', ())
+        s = self._create_object(Service, **kwargs)
+        s.resources = resources
+        return self._list(Service, filter=(s.id,))
+
+    @safe
+    def remove_services(self, ids=()):
+        # TODO return information for unknown ids
+        q = Service.objects.filter(id__in=ids)
+        q.delete()
+    
+    @safe
+    def update_service(self, service_id, renew_token=False, **kwargs):
+        s = self._update_object(Service, service_id, save=False, **kwargs)
+        if renew_token:
+            s.renew_token()
+
+        if kwargs or renew_token:
+            s.save()
+
+    @safe
+    def add_resources(self, service_id, update=False, resources=()):
+        s = self._lookup_service(service_id)
+        rejected = []
+        append = rejected.append
+        for r in resources:
+            try:
+                rr = r.copy()
+                resource_id = rr.pop('id', None)
+                if update:
+                    if not resource_id:
+                        raise MissingIdentifier
+                    resource = self._update_object(Resource, resource_id, **rr)
+                else:
+                    resource = self._create_object(Resource, service=s, **rr)
+            except Exception, e:
+                append((r, e))
+        return rejected
+    
+    @safe
+    def remove_resources(self, service_id, ids=()):
+        # TODO return information for unknown ids
+        q = Resource.objects.filter(service__id=service_id,
+                                id__in=ids)
+        q.delete()
+    
+    @safe
+    def create_group(self, **kwargs):
+        policies = kwargs.pop('policies', ())
+        permissions = kwargs.pop('permissions', ())
+        members = kwargs.pop('members', ())
+        owners = kwargs.pop('owners', ())
+
+        g = self._create_object(AstakosGroup, **kwargs)
+
+        g.permissions = permissions
+        g.policies = policies
+#         g.members = members
+        g.owners = owners
+        return self._list(AstakosGroup, filter=(g.id,))
diff --git a/snf-astakos-app/astakos/im/api/backends/notifications.py b/snf-astakos-app/astakos/im/api/backends/notifications.py
new file mode 100644 (file)
index 0000000..cd72179
--- /dev/null
@@ -0,0 +1,97 @@
+# Copyright 2011-2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+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
+        )
+
+class Notification(object):
+    def __init__(self, sender, recipients, subject, message):
+        self.sender = sender
+        self.recipients = recipients
+        self.subject = subject
+        self.message = message
+    
+    def send(self):
+        pass
+
+class SendMailError(Exception):
+    pass
+
+class SendNotificationError(SendMailError):
+    def __init__(self):
+        self.message = _(astakos_messages.NOTIFICATION_SEND_ERR)
+        super(SendNotificationError, self).__init__()
diff --git a/snf-astakos-app/astakos/im/api/callpoint.py b/snf-astakos-app/astakos/im/api/callpoint.py
new file mode 100644 (file)
index 0000000..fa0c90c
--- /dev/null
@@ -0,0 +1,151 @@
+# Copyright 2011-2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from astakos.im.api.spec import AstakosAPI
+from astakos.im.api.backends import get_backend
+
+from commissioning import CorruptedError
+
+from django.db import transaction
+
+class AstakosCallpoint():
+
+    api_spec = AstakosAPI()
+
+#     http_exc_lookup = {
+#         CorruptedError:   550,
+#         InvalidDataError: 400,
+#         InvalidKeyError:  401,
+#         NoEntityError:    404,
+#         NoQuantityError:  413,
+#         NoCapacityError:  413,
+#     }
+
+    def init_connection(self, connection):
+        if connection is not None:
+            raise ValueError("Cannot specify connection args with %s" %
+                             type(self).__name__)
+        pass
+
+    def commit(self):
+        transaction.commit()
+
+    def rollback(self):
+        transaction.rollback()
+
+    def do_make_call(self, call_name, data):
+        call_fn = getattr(self, call_name, None)
+        if not call_fn:
+            m = "cannot find call '%s'" % (call_name,)
+            raise CorruptedError(m)
+
+        return call_fn(**data)
+
+    def create_users(self, users=()):
+        b = get_backend()
+        rejected = (b.create_user(**u) for u in users)
+        return rejected
+
+    def update_users(self, users=()):
+        b = get_backend()
+        rejected = (b.update_user(**u) for u in users)
+        return rejected
+
+    def add_user_policies(self, user_id, update=False, policies=()):
+        b = get_backend()
+        rejected = b.add_policies(user_id, update, policies)
+        return rejected
+
+    def remove_user_policies(self, user_id, policies=()):
+        b = get_backend()
+        rejected = b.remove_policies(user_id, policies)
+        return rejected
+
+    def add_user_permissions(self, user_id, permissions=()):
+        b = get_backend()
+        rejected = b.add_permissions(user_id, permissions)
+        return rejected
+
+    def remove_user_permissions(self, user_id, permissions=()):
+        b = get_backend()
+        rejected = b.remove_permissions(user_id, permissions)
+        return rejected
+
+    def invite_users(self, sender_id, recipients=()):
+        b = get_backend()
+        rejected = b.invite_users(sender_id, recipients)
+        return rejected
+
+    def list_users(self, filter=()):
+        b = get_backend()
+        return b.list_users(filter)
+
+    def get_user_status(self, user_id):
+        b = get_backend()
+        return b.get_resource_usage(user_id)
+
+    def list_resources(self, filter=()):
+        b = get_backend()
+        return b.list_resources(filter)
+
+    def add_services(self, services=()):
+        b = get_backend()
+        rejected = (b.create_service(**s) for s in services)
+        return rejected
+
+    def update_services(self, services=()):
+        b = get_backend()
+        rejected = (b.update_service(**s) for s in services)
+        return rejected
+
+    def remove_services(self, ids=()):
+        b = get_backend()
+        rejected = b.remove_services(ids)
+        return rejected
+
+    def add_resources(self, service_id, update=False, resources=()):
+        b = get_backend()
+        rejected = b.add_resources(service_id, update, resources)
+        return rejected
+    
+    def remove_resources(self, service_id, ids=()):
+        b = get_backend()
+        rejected = b.remove_resources(service_id, ids)
+        return rejected
+    
+    def create_groups(self, groups=()):
+        b = get_backend()
+        rejected = (b.create_group(**g) for g in groups)
+        return rejected
+
+API_Callpoint = AstakosCallpoint
diff --git a/snf-astakos-app/astakos/im/api/client.py b/snf-astakos-app/astakos/im/api/client.py
new file mode 100644 (file)
index 0000000..192b38b
--- /dev/null
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+from commissioning.clients.http import main, HTTP_API_Client
+from astakos.im.api.spec import AstakosAPI
+
+
+class AstakosHTTP(HTTP_API_Client):
+    api_spec = AstakosAPI()
+
+
+if __name__ == '__main__':
+    main(callpoint=AstakosHTTP())
index 72408f1..a7ffb66 100644 (file)
@@ -1,18 +1,18 @@
 # Copyright 2011-2012 GRNET S.A. All rights reserved.
-# 
+#
 # Redistribution and use in source and binary forms, with or
 # without modification, are permitted provided that the following
 # conditions are met:
-# 
+#
 #   1. Redistributions of source code must retain the above
 #      copyright notice, this list of conditions and the following
 #      disclaimer.
-# 
+#
 #   2. Redistributions in binary form must reproduce the above
 #      copyright notice, this list of conditions and the following
 #      disclaimer in the documentation and/or other materials
 #      provided with the distribution.
-# 
+#
 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 # POSSIBILITY OF SUCH DAMAGE.
-# 
+#
 # The views and conclusions contained in the software and
 # documentation are those of the authors and should not be
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
+
 def camelCase(s):
     return s[0].lower() + s[1:]
 
+
 class Fault(Exception):
     def __init__(self, message='', details='', name=''):
         Exception.__init__(self, message, details, name)
@@ -41,17 +43,22 @@ class Fault(Exception):
         self.details = details
         self.name = name or camelCase(self.__class__.__name__)
 
+
 class BadRequest(Fault):
     code = 400
 
+
 class Unauthorized(Fault):
     code = 401
 
+
 class InternalServerError(Fault):
     code = 500
 
+
 class Forbidden(Fault):
     code = 403
 
+
 class ItemNotFound(Fault):
-    code = 404
\ No newline at end of file
+    code = 404
index fd2be50..2f81e71 100644 (file)
 # or implied, of GRNET S.A.
 
 import logging
-import urllib
 
 from functools import wraps
-from traceback import format_exc
 from time import time, mktime
-from urllib import quote
-from urlparse import urlparse
-from collections import defaultdict
 
-from django.conf import settings
 from django.http import HttpResponse
-from django.core.urlresolvers import reverse
 from django.views.decorators.csrf import csrf_exempt
 
-from astakos.im.api.faults import *
+from astakos.im.api.faults import Fault, Unauthorized, InternalServerError, BadRequest
+from astakos.im.api import render_fault, _get_user_by_email, _get_user_by_username
 from astakos.im.models import AstakosUser, Service
-from astakos.im.settings import INVITATIONS_ENABLED, COOKIE_NAME, EMAILCHANGE_ENABLED
-from astakos.im.util import epoch
 from astakos.im.forms import FeedbackForm
-from astakos.im.functions import send_feedback as send_feedback_func, SendMailError
+from astakos.im.functions import send_feedback as send_feedback_func
 
 logger = logging.getLogger(__name__)
 
-def render_fault(request, fault):
-    if isinstance(fault, InternalServerError) and settings.DEBUG:
-        fault.details = format_exc(fault)
-
-    request.serialization = 'text'
-    data = fault.message + '\n'
-    if fault.details:
-        data += '\n' + fault.details
-    response = HttpResponse(data, status=fault.code)
-    response['Content-Length'] = len(response.content)
-    return response
 
 def api_method(http_method=None, token_required=False):
     """Decorator function for views that implement an API method."""
@@ -81,7 +62,7 @@ def api_method(http_method=None, token_required=False):
                         raise Unauthorized('Access denied')
                     try:
                         service = Service.objects.get(auth_token=x_auth_token)
-                        
+
                         # Check if the token has expired.
                         if (time() - mktime(service.auth_token_expires.timetuple())) > 0:
                             raise Unauthorized('Authentication expired')
@@ -98,6 +79,7 @@ def api_method(http_method=None, token_required=False):
         return wrapper
     return decorator
 
+
 @api_method(http_method='GET', token_required=True)
 def get_user_by_email(request, user=None):
     # Normal Response Codes: 200
@@ -109,6 +91,7 @@ def get_user_by_email(request, user=None):
     email = request.GET.get('name')
     return _get_user_by_email(email)
 
+
 @api_method(http_method='GET', token_required=True)
 def get_user_by_username(request, user_id, user=None):
     # Normal Response Codes: 200
@@ -119,6 +102,7 @@ def get_user_by_username(request, user_id, user=None):
     #                       itemNotFound (404)
     return _get_user_by_username(user_id)
 
+
 @csrf_exempt
 @api_method(http_method='POST', token_required=True)
 def send_feedback(request, email_template_name='im/feedback_mail.txt'):
@@ -129,23 +113,23 @@ def send_feedback(request, email_template_name='im/feedback_mail.txt'):
     auth_token = request.POST.get('auth', '')
     if not auth_token:
         raise BadRequest('Missing user authentication')
-    
-    user  = None
+
+    user = None
     try:
         user = AstakosUser.objects.get(auth_token=auth_token)
     except:
         pass
-    
+
     if not user:
         raise BadRequest('Invalid user authentication')
-    
+
     form = FeedbackForm(request.POST)
     if not form.is_valid():
         raise BadRequest('Invalid data')
-    
+
     msg = form.cleaned_data['feedback_msg']
     data = form.cleaned_data['feedback_data']
     send_feedback_func(msg, data, user, email_template_name)
     response = HttpResponse(status=200)
     response['Content-Length'] = len(response.content)
-    return response
\ No newline at end of file
+    return response
diff --git a/snf-astakos-app/astakos/im/api/spec.py b/snf-astakos-app/astakos/im/api/spec.py
new file mode 100644 (file)
index 0000000..f355856
--- /dev/null
@@ -0,0 +1,420 @@
+from commissioning.specificator import (
+    Specificator, Integer, Text, ListOf
+)
+
+
+class Name(Text):
+    def init(self):
+        self.opts.update({'regex': "[\w.:]+", 'maxlen': 512})
+Name = Name()
+
+
+class Email(Text):
+    def init(self):
+        pattern = "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"
+        self.opts.update({'regex': pattern, 'maxlen': 512})
+Email = Email()
+
+
+class Url(Text):
+    def init(self):
+        pattern = "(((f|ht){1}tp://)[-a-zA-Z0-9@:%_\+.~#?&//=]+)"
+        self.opts.update({'regex': pattern, 'maxlen': 512})
+Url = Url()
+
+
+class Filepath(Text):
+    def init(self):
+        self.opts.update({'regex': "", 'maxlen': 512})
+Filepath = Filepath()
+
+
+class Nonnegative(Integer):
+    def init(self):
+        self.opts.update({'minimum': 0})
+Nonnegative = Nonnegative()
+
+
+class Boolean(Integer):
+    def init(self):
+        self.opts.update({'minimum': 0, 'maximum': 1})
+Boolean = Boolean()
+
+
+# class GroupKind(Integer):
+#     def init(self):
+#         self.opts.update({'minimum': 1, 'maximum': 5})
+# GroupKind = GroupKind()
+
+Timepoint = Text(classname='Timepoint', maxlen=24)
+
+
+class AstakosAPI(Specificator):
+    def create_users(
+        self,
+        users=ListOf(
+            email=Email,
+            first_name=Name,
+            last_name=Name,
+            is_active=Boolean,
+            is_superuser=Boolean,
+            affiliation=Name,
+            password=Name,
+            provider=Name,
+            level=Nonnegative,
+            invitations=Nonnegative,
+            is_verified=Boolean,
+            third_party_identifier=Name,
+            email_verified=Boolean),
+        policies=ListOf(resource=Name, supimit=Nonnegative),
+        groups=ListOf(Name),
+        permissions=ListOf(Name)
+    ):
+        rejected = ListOf(user=Email, reason=Text())
+        return rejected
+
+    def update_users(
+        self,
+        users=ListOf(
+            pk=Nonnegative,
+            renew_token=Boolean,
+            data=ListOf(
+                first_name=Name,
+                last_name=Name,
+                is_active=Boolean,
+                is_superuser=Boolean,
+                affiliation=Name,
+                password=Name,
+                provider=Name,
+                level=Nonnegative,
+                invitations=Nonnegative,
+                is_verified=Boolean,
+                third_party_identifier=Name,
+                email_verified=Boolean
+            )
+        )
+    ):
+        rejected = ListOf(user_id=Nonnegative, reason=Text())
+        return rejected
+
+    def add_user_policies(
+        self,
+        pk=Nonnegative,
+        update=Boolean,
+        policies=ListOf(service=Name, resource=Name, upimit=Nonnegative)
+    ):
+        rejected = ListOf(resource=Name, reason=Text())
+        return rejected
+
+    def remove_user_policies(
+        self,
+        pk=Nonnegative,
+        policies=ListOf(service=Name, resource=Name)
+    ):
+        rejected = ListOf(service=Name, resource=Name)
+        return rejected
+
+    def add_user_permissions(
+        self,
+        pk=Nonnegative,
+        permissions=ListOf(permission=Name)
+    ):
+        rejected = ListOf(permission=Name)
+        return rejected
+
+    def remove_user_permissions(
+        self,
+        pk=Nonnegative,
+        permissions=ListOf(permission=Name)
+    ):
+        rejected = ListOf(permission=Name)
+        return rejected
+
+    def invite_users(
+        self,
+        sender=Email,
+        data=ListOf(email=Email, realname=Name)
+    ):
+        rejected = ListOf(receiver=Email)
+        return rejected
+
+    def list_users(
+        self,
+        filter=ListOf(id=Nonnegative)
+    ):
+        return ListOf(
+            activation_sent=Timepoint,
+            affiliation=Name,
+            auth_token=Name,
+            auth_token_created=Timepoint,
+            auth_token_expires=Timepoint,
+            date_joined=Timepoint,
+            date_signed_terms=Timepoint,
+            email=Email,
+            email_verified=Boolean,
+            first_name=Name,
+            has_credits=Boolean,
+            has_signed_terms=Boolean,
+            id=Nonnegative,
+            invitations=Nonnegative,
+            invitations_sent=ListOf(
+                code=Name,
+                consumed=Boolean,
+                created=Timepoint,
+                id=Nonnegative,
+                realname=Name,
+                username=Email
+            ),
+            is_active=Boolean,
+            is_staff=Boolean,
+            is_superuser=Boolean,
+            is_verified=Boolean,
+            last_login=Timepoint,
+            last_name=Name,
+            level=Nonnegative,
+            password=Name,
+            provider=Name,
+            third_party_identifier=Name,
+            updated=Timepoint,
+            user_permissions=ListOf(
+                codename=Name,
+                id=Nonnegative,
+                name=Name
+            ),
+            username=Name,
+            astakos_groups=ListOf(
+                approval_date=Timepoint,
+                creation_date=Timepoint,
+                desc=Text(),
+                max_participants=Nonnegative,
+                expiration_date=Timepoint,
+                group_ptr=Url,
+                homepage=Url,
+                id=Nonnegative,
+                issue_date=Timepoint,
+                kind=Name,
+                moderation_enabled=Boolean,
+                name=Name,
+                #permissions=ListOf(),
+                policy=ListOf(id=Nonnegative, name=Name)
+            )
+        )
+
+    def get_user_status(
+        self,
+        user_id=Nonnegative
+    ):
+        return ListOf(
+            name=Name,
+            description=Text(),
+            unit=Name,
+            maxValue=Integer(),
+            currValue=Integer()
+        )
+
+    def list_resources(self, filter=ListOf(id=Nonnegative)):
+        return ListOf(
+            desc=Text(),
+            group=Name,
+            id=Nonnegative,
+            meta=ListOf(key=Name, value=Name),
+            name=Name,
+            service=Name,
+            unit=Name
+        )
+
+    def add_services(
+        self,
+        services=ListOf(
+            name=Name,
+            url=Url,
+            icon=Filepath,
+            resources=ListOf(
+                name=Name,
+                desc=Text(),
+                unit=Name,
+                group=Name
+            )
+        )
+    ):
+        rejected = ListOf(service=Name)
+        return rejected
+
+    def update_services(
+        self,
+        services=ListOf(id=Nonnegative, url=Url, icon=Filepath)
+    ):
+        rejected = ListOf(service=Name)
+        return rejected
+
+    def remove_services(self, ids=ListOf(Nonnegative)):
+        rejected = ListOf(service=Name)
+        return rejected
+
+    def add_resources(
+        self,
+        service_id=Nonnegative,
+        update=Boolean,
+        resources=ListOf(
+            name=Name,
+            resources=ListOf(
+                name=Name,
+                desc=Text(),
+                unit=Name,
+                group=Name)
+        )
+    ):
+        rejected = ListOf(service=Name)
+        return rejected
+
+    def remove_resources(
+        self,
+        service_id=Nonnegative,
+        ids=ListOf(Nonnegative)
+    ):
+        rejected = ListOf(Name)
+        return rejected
+
+    def create_groups(
+        self,
+        groups=ListOf(
+            name=Name,
+            kind=Name,
+            homepage=Url,
+            desc=Text(),
+            policies=ListOf(resource=Name, upimit=Nonnegative),
+            issue_date=Timepoint,
+            expiration_date=Timepoint,
+            moderation_enabled=Boolean,
+            participants=Nonnegative,
+            permissions=ListOf(permission=Name),
+            members=ListOf(user=Email, is_approved=Boolean),
+            owners=ListOf(user=Email)
+        )
+    ):
+        rejected = ListOf(group=Name)
+        return rejected
+
+    def enable_groups(self, data=ListOf(group=Name)):
+        rejected = ListOf(group=Name)
+        return rejected
+
+    def search_groups(self, key=Name):
+        return ListOf(
+            group=Name,
+            kind=Nonnegative,
+            homepage=Url,
+            desc=Text(),
+            creation_date=Timepoint,
+            issue_date=Timepoint,
+            expiration_date=Timepoint,
+            moderation_enabled=Boolean,
+            participants=Nonnegative,
+            owner=ListOf(user=Email),
+            policies=ListOf(resource=Name, upimit=Nonnegative),
+            members=ListOf(user=Email, is_approved=Boolean)
+        )
+
+    def list_groups(self):
+        return ListOf(
+            group=Name,
+            kind=Nonnegative,
+            homepage=Url,
+            desc=Text(),
+            creation_date=Timepoint,
+            issue_date=Timepoint,
+            expiration_date=Timepoint,
+            moderation_enabled=Boolean,
+            participants=Nonnegative,
+            owners=ListOf(user=Email),
+            policies=ListOf(resource=Name, upimit=Nonnegative),
+            members=ListOf(user=Email, is_approved=Boolean)
+        )
+
+    def add_owners(
+        self,
+        data=ListOf(group=Name, owners=ListOf(user=Email))
+    ):
+        rejected = ListOf(user=Email)
+        return rejected
+
+    def remove_owners(
+        self,
+        data=ListOf(group=Name, owners=ListOf(user=Email))
+    ):
+        rejected = ListOf(user=Email)
+        return rejected
+
+    def add_members(
+        self,
+        data=ListOf(group=Name, members=ListOf(user=Email))
+    ):
+        rejected = ListOf(user=Email)
+        return rejected
+
+    def remove_members(
+        self,
+        data=ListOf(group=Name, members=ListOf(user=Email))
+    ):
+        rejected = ListOf(user=Email)
+        return rejected
+
+    def add_policies(
+        self,
+        data=ListOf(group=Name, resource=Name, upimit=Nonnegative)
+    ):
+        rejected = ListOf(group=Name, resource=Name)
+        return rejected
+
+    def remove_group_policies(
+        self,
+        data=ListOf(group=Name, resource=Name, upimit=Nonnegative)
+    ):
+        rejected = ListOf(group=Name, resource=Name)
+        return rejected
+
+    def update_group_policies(
+        self, data=ListOf(group=Name, resource=Name, upimit=Nonnegative)
+    ):
+        rejected = ListOf(group=Name, resource=Name)
+        return rejected
+
+    def approve_members(
+        self,
+        data=ListOf(group=Name, members=ListOf(user=Email))
+    ):
+        rejected = ListOf(user=Email)
+        return rejected
+
+    def disapprove_members(
+        self,
+        data=ListOf(group=Name, members=ListOf(user=Email))
+    ):
+        rejected = ListOf(user=Email)
+        return rejected
+
+    def add_group_permissions(
+        self,
+        data=ListOf(group=Name, permission=Name)
+    ):
+        rejected = ListOf(group=Name, permission=Name)
+        return rejected
+
+    def delete_group_permissions(
+        self,
+        data=ListOf(group=Name, permission=Name)
+    ):
+        rejected = ListOf(group=Name, permission=Name)
+        return rejected
+
+    def list_resource_units(self):
+        return ListOf(Name)
+
+    def get_approval_terms(self, term=Nonnegative):
+        return Text()
+
+    def add_approval_terms(self, location=Filepath):
+        return Nonnegative
+
+#     def change_emails():
+#         pass
index 8a6354d..515f864 100644 (file)
@@ -41,6 +41,7 @@ import logging
 
 logger = logging.getLogger(__name__)
 
+
 class TokenBackend(ModelBackend):
     """
     AuthenticationBackend used to authenticate using token instead
@@ -62,11 +63,12 @@ class TokenBackend(ModelBackend):
         except AstakosUser.DoesNotExist:
             return None
 
+
 class EmailBackend(ModelBackend):
     """
     If the ``username`` parameter is actually an email uses email to authenticate
     the user else tries the username.
-    
+
     Used from ``astakos.im.forms.LoginForm`` to authenticate.
     """
     def authenticate(self, username=None, password=None):
@@ -90,10 +92,10 @@ class EmailBackend(ModelBackend):
         else:
             msg = 'Invalid password during authentication for %s' % username
             logger._log(LOGGING_LEVEL, msg, [])
-            
-    
+
+
     def get_user(self, user_id):
         try:
             return AstakosUser.objects.get(pk=user_id)
         except AstakosUser.DoesNotExist:
-            return None
\ No newline at end of file
+            return None
index 31ba885..1c283ef 100644 (file)
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
-from astakos.im.settings import IM_MODULES, INVITATIONS_ENABLED, IM_STATIC_URL, \
-        COOKIE_NAME, LOGIN_MESSAGES, SIGNUP_MESSAGES, PROFILE_MESSAGES, \
-        GLOBAL_MESSAGES, PROFILE_EXTRA_LINKS
-from astakos.im.api.admin import get_menu
+from astakos.im.settings import (
+    IM_MODULES, INVITATIONS_ENABLED, IM_STATIC_URL,
+    LOGIN_MESSAGES, SIGNUP_MESSAGES, PROFILE_MESSAGES,
+    GLOBAL_MESSAGES, PROFILE_EXTRA_LINKS
+)
+from astakos.im.api import get_menu
 from astakos.im.util import get_query
+from astakos.im.models import GroupKind
 
-from django.conf import settings
-from django.core.urlresolvers import reverse
 from django.utils import simplejson as json
 
+
 def im_modules(request):
     return {'im_modules': IM_MODULES}
 
+
 def next(request):
-    return {'next' : get_query(request).get('next', '')}
+    return {'next': get_query(request).get('next', '')}
+
 
 def code(request):
-    return {'code' : request.GET.get('code', '')}
+    return {'code': request.GET.get('code', '')}
+
 
 def invitations(request):
-    return {'invitations_enabled' :INVITATIONS_ENABLED}
+    return {'invitations_enabled': INVITATIONS_ENABLED}
+
 
 def media(request):
-    return {'IM_STATIC_URL' : IM_STATIC_URL}
+    return {'IM_STATIC_URL': IM_STATIC_URL}
+
 
 def custom_messages(request):
     global GLOBAL_MESSAGES, SIGNUP_MESSAGES, LOGIN_MESSAGES, PROFILE_MESSAGES
@@ -69,26 +76,29 @@ def custom_messages(request):
     if type(PROFILE_MESSAGES) == dict:
         PROFILE_MESSAGES = PROFILE_MESSAGES.items()
 
-    EXTRA_MESSAGES_SET = bool(GLOBAL_MESSAGES or SIGNUP_MESSAGES or \
-            LOGIN_MESSAGES or PROFILE_MESSAGES)
+    EXTRA_MESSAGES_SET = bool(GLOBAL_MESSAGES or SIGNUP_MESSAGES or
+                              LOGIN_MESSAGES or PROFILE_MESSAGES)
 
     return {
-            'GLOBAL_MESSAGES' : GLOBAL_MESSAGES,
-            'SIGNUP_MESSAGES' : SIGNUP_MESSAGES,
-            'LOGIN_MESSAGES' : LOGIN_MESSAGES,
-            'PROFILE_MESSAGES' : PROFILE_MESSAGES,
-            'PROFILE_EXTRA_LINKS' : PROFILE_EXTRA_LINKS,
-            'EXTRA_MESSAGES_SET' : EXTRA_MESSAGES_SET
-           }
+        'GLOBAL_MESSAGES': GLOBAL_MESSAGES,
+        'SIGNUP_MESSAGES': SIGNUP_MESSAGES,
+        'LOGIN_MESSAGES': LOGIN_MESSAGES,
+        'PROFILE_MESSAGES': PROFILE_MESSAGES,
+        'PROFILE_EXTRA_LINKS': PROFILE_EXTRA_LINKS,
+        'EXTRA_MESSAGES_SET': EXTRA_MESSAGES_SET
+    }
+
 
 def menu(request):
-    absolute = lambda (url): request.build_absolute_uri(url)
-    resp = get_menu(request, True, False)
     try:
+        resp = get_menu(request, True, False)
         menu_items = json.loads(resp.content)[1:]
     except Exception, e:
         return {}
     else:
-        for item in menu_items:
-            item['is_active'] = absolute(request.path) == item['url']
-        return {'menu':menu_items}
+        return {'menu': menu_items}
+
+
+def group_kinds(request):
+    return {'group_kinds': GroupKind.objects.exclude(
+        name='default').values_list('name', flat=True)}
index 8dca8ff..47a4a4c 100644 (file)
@@ -43,6 +43,8 @@ from astakos.im.settings import (
     COOKIE_NAME, COOKIE_DOMAIN, COOKIE_SECURE, LOGGING_LEVEL
 )
 
+import astakos.im.messages as astakos_messages
+
 logger = logging.getLogger(__name__)
 
 class Cookie():
@@ -63,7 +65,7 @@ class Cookie():
     
     @property
     def is_set(self):
-        no_token = not self.auth_token 
+        no_token = not self.auth_token
         return not no_token
     
     @property
@@ -77,7 +79,7 @@ class Cookie():
     
     def __set(self):
         if not self.response:
-            raise ValueError(_('There is no response.'))
+            raise ValueError(_(astakos_messages.NO_RESPONSE))
         user = self.user
         expire_fmt = user.auth_token_expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
         cookie_value = quote(user.email + '|' + user.auth_token)
@@ -90,7 +92,7 @@ class Cookie():
     
     def __delete(self):
         if not self.response:
-            raise ValueError(_('There is no response.'))
+            raise ValueError(_(astakos_messages.NO_RESPONSE))
         self.response.delete_cookie(COOKIE_NAME, path='/', domain=COOKIE_DOMAIN)
         msg = 'Cookie deleted for %(email)s' % self.__dict__
         logger._log(LOGGING_LEVEL, msg, [])
diff --git a/snf-astakos-app/astakos/im/endpoints/__init__.py b/snf-astakos-app/astakos/im/endpoints/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/snf-astakos-app/astakos/im/endpoints/aquarium/__init__.py b/snf-astakos-app/astakos/im/endpoints/aquarium/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
-import json
+import requests
 
-from time import time
-from hashlib import sha1
-from random import random
+from django.utils import simplejson as json
 
-class UserEvent(object):
-    def __init__(self, client, user, eventType, details={}):
-        self.eventVersion = '1'
-        self.occurredMillis = int(time() * 1000)
-        self.receivedMillis = self.occurredMillis
-        self.clientID = client
-        self.userID = user.email
-        self.isActive = user.is_active
-        self.role = 'default'
-        self.eventType = eventType
-        self.details = details
-        hash = sha1()
-        hash.update(json.dumps([client, self.userID, self.isActive, self.role,
-                                self.eventType, self.details, self.occurredMillis]))
-        self.id = hash.hexdigest()
-    
-    def format(self):
-        return self.__dict__
\ No newline at end of file
+from astakos.im.settings import AQUARIUM_URL
+
+
+class AquariumClient():
+    def get_billing(self, user, start, end):
+        if not AQUARIUM_URL:
+            return
+
+        url = AQUARIUM_URL.rstrip('/')
+        url = '%s/%s/bill/%d/%d' % (url, user, start, end)
+        r = requests.get(url)
+        try:
+            return r.status_code, json.loads(r.text)
+        except ValueError:
+            pass
+        return r.status_code, None
diff --git a/snf-astakos-app/astakos/im/endpoints/aquarium/consumer.py b/snf-astakos-app/astakos/im/endpoints/aquarium/consumer.py
new file mode 100644 (file)
index 0000000..db228f7
--- /dev/null
@@ -0,0 +1,55 @@
+# Copyright 2011-2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+import logging
+
+logging.basicConfig(format='%(asctime)s [%(levelname)s] %(name)s %(message)s',
+                    datefmt='%Y-%m-%d %H:%M:%S'
+                    )
+logger = logging.getLogger('endpoint.aquarium')
+
+from astakos.im.models import AstakosUser
+
+
+def on_creditevent(msg):
+    """
+    Queue handler for updating AstakosUser has_credits
+    """
+    try:
+        email = msg.get('userid')
+        has_credits = msg.get('status') == 'on' or False
+        user = AstakosUser.objects.get(email=email, is_active=True)
+        user.has_credits = has_credits
+        user.save()
+    except BaseException, e:
+        logger.exception(e)
diff --git a/snf-astakos-app/astakos/im/endpoints/aquarium/producer.py b/snf-astakos-app/astakos/im/endpoints/aquarium/producer.py
new file mode 100644 (file)
index 0000000..6ea4f26
--- /dev/null
@@ -0,0 +1,97 @@
+# Copyright 2011-2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+import logging
+
+from functools import wraps
+from urlparse import urlparse
+
+from astakos.im.settings import QUEUE_CONNECTION
+
+if QUEUE_CONNECTION:
+    from synnefo.lib.queue import (exchange_connect, exchange_send,
+                                   exchange_close, UserEvent, Receipt
+                                   )
+
+QUEUE_CLIENT_ID = '3'  # Astakos.
+INSTANCE_ID = '1'
+RESOURCE = 'addcredits'
+DEFAULT_CREDITS = 1000
+
+logging.basicConfig(format='%(asctime)s [%(levelname)s] %(name)s %(message)s',
+                    datefmt='%Y-%m-%d %H:%M:%S'
+                    )
+logger = logging.getLogger('endpoint.aquarium')
+
+
+def call(func):
+    @wraps(func)
+    def wrapper(*args, **kwargs):
+        if not QUEUE_CONNECTION:
+            return
+
+        try:
+            body, key = func(*args, **kwargs)
+            conn = exchange_connect(QUEUE_CONNECTION)
+            parts = urlparse(QUEUE_CONNECTION)
+            exchange = parts.path[1:]
+            routing_key = key % exchange
+            exchange_send(conn, routing_key, body)
+            exchange_close(conn)
+        except BaseException, e:
+            logger.exception(e)
+    return wrapper
+
+
+@call
+def report_user_event(user, create=False):
+    eventType = 'create' if not create else 'modify'
+    body = UserEvent(QUEUE_CLIENT_ID, user.email, user.is_active, eventType, {}
+                     ).format()
+    routing_key = '%s.user'
+    return body, routing_key
+
+
+@call
+def report_user_credits_event(user):
+    body = Receipt(QUEUE_CLIENT_ID, user.email, INSTANCE_ID, RESOURCE,
+                   DEFAULT_CREDITS, details={}
+                   ).format()
+    routing_key = '%s.resource'
+    return body, routing_key
+
+
+def report_credits_event():
+    # better approach?
+    from astakos.im.models import AstakosUser
+    map(report_user_credits_event, AstakosUser.objects.all())
diff --git a/snf-astakos-app/astakos/im/endpoints/qh.py b/snf-astakos-app/astakos/im/endpoints/qh.py
new file mode 100644 (file)
index 0000000..648c7dd
--- /dev/null
@@ -0,0 +1,295 @@
+# Copyright 2011-2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+import logging
+import itertools
+
+from functools import wraps
+
+from django.utils.translation import ugettext as _
+
+from astakos.im.settings import QUOTA_HOLDER_URL, LOGGING_LEVEL
+
+if QUOTA_HOLDER_URL:
+    from kamaki.clients.quotaholder import QuotaholderClient
+
+ENTITY_KEY = '1'
+
+inf = float('inf')
+
+logger = logging.getLogger(__name__)
+
+inf = float('inf')
+
+def call(func_name):
+    """Decorator function for Quotaholder client calls."""
+    def decorator(payload_func):
+        @wraps(payload_func)
+        def wrapper(entities=(), client=None, **kwargs):
+            if not entities:
+                return client, ()
+
+            if not QUOTA_HOLDER_URL:
+                return client, ()
+
+            c = client or QuotaholderClient(QUOTA_HOLDER_URL)
+            func = c.__dict__.get(func_name)
+            if not func:
+                return c, ()
+
+            data = payload_func(entities, client, **kwargs)
+            if not data:
+                return c, data
+
+            funcname = func.__name__
+            kwargs = {'context': {}, funcname: data}
+            rejected = func(**kwargs)
+            msg = _('%s: %s - Rejected: %s' % (funcname, data, rejected,))
+            logger.log(LOGGING_LEVEL, msg)
+            return c, rejected
+        return wrapper
+    return decorator
+
+
+@call('set_quota')
+def send_quota(users, client=None):
+    data = []
+    append = data.append
+    for user in users:
+        for resource, uplimit in user.quota.iteritems():
+            key = ENTITY_KEY
+            quantity = None
+            capacity = uplimit if uplimit != inf else None
+            import_limit = None
+            export_limit = None
+            flags = 0
+            args = (
+                user.email, resource, key, quantity, capacity, import_limit,
+                export_limit, flags)
+            append(args)
+    return data
+
+
+@call('set_quota')
+def send_resource_quantities(resources, client=None):
+    data = []
+    append = data.append
+    for resource in resources:
+        key = ENTITY_KEY
+        quantity = resource.meta.filter(key='quantity') or None
+        capacity = None
+        import_limit = None
+        export_limit = None
+        flags = 0
+        args = (resource.service, resource, key, quantity, capacity,
+                import_limit, export_limit, flags)
+        append(args)
+    return data
+
+
+@call('get_quota')
+def get_quota(users, client=None):
+    data = []
+    append = data.append
+    for user in users:
+        try:
+            entity = user.email
+        except AttributeError:
+            continue
+        else:
+            for r in user.quota.keys():
+                args = entity, r, ENTITY_KEY
+                append(args)
+    return data
+
+
+@call('create_entity')
+def create_entities(entities, client=None, field=''):
+    data = []
+    append = data.append
+    for entity in entities:
+        try:
+            entity = entity.__getattribute__(field)
+        except AttributeError:
+            continue
+        owner = 'system'
+        key = ENTITY_KEY
+        ownerkey = ''
+        args = entity, owner, key, ownerkey
+        append(args)
+    return data
+
+
+def register_users(users, client=None):
+    users, copy = itertools.tee(users)
+    client, rejected = create_entities(entities=users,
+                                       client=client,
+                                       field='email')
+    created = (e for e in copy if unicode(e) not in rejected)
+    return send_quota(created, client)
+
+
+def register_resources(resources, client=None):
+    resources, copy = itertools.tee(resources)
+    client, rejected = create_entities(entities=resources,
+                                       client=client,
+                                       field='service')
+    created = (e for e in copy if unicode(e) not in rejected)
+    return send_resource_quantities(created, client)
+
+
+from datetime import datetime
+
+strptime = datetime.strptime
+timefmt = '%Y-%m-%dT%H:%M:%S.%f'
+
+SECOND_RESOLUTION = 1
+
+
+def total_seconds(timedelta_object):
+    return timedelta_object.seconds + timedelta_object.days * 86400
+
+
+def iter_timeline(timeline, before):
+    if not timeline:
+        return
+
+    for t in timeline:
+        yield t
+
+    t = dict(t)
+    t['issue_time'] = before
+    yield t
+
+
+def _usage_units(timeline, after, before, details=0):
+
+    t_total = 0
+    uu_total = 0
+    t_after = strptime(after, timefmt)
+    t_before = strptime(before, timefmt)
+    t0 = t_after
+    u0 = 0
+
+    for point in iter_timeline(timeline, before):
+        issue_time = point['issue_time']
+
+        if issue_time <= after:
+            u0 = point['target_allocated_through']
+            continue
+
+        t = strptime(issue_time, timefmt) if issue_time <= before else t_before
+        t_diff = int(total_seconds(t - t0) * SECOND_RESOLUTION)
+        t_total += t_diff
+        uu_cost = u0 * t_diff
+        uu_total += uu_cost
+        t0 = t
+        u0 = point['target_allocated_through']
+
+        target = point['target']
+        if details:
+            yield  (target,
+                    point['resource'],
+                    point['name'],
+                    issue_time,
+                    uu_cost,
+                    uu_total)
+
+    if not t_total:
+        return
+
+    yield  (target,
+            'total',
+            point['resource'],
+            issue_time,
+            uu_total / t_total,
+            uu_total)
+
+
+def usage_units(timeline, after, before, details=0):
+    return list(_usage_units(timeline, after, before, details=details))
+
+
+def traffic_units(timeline, after, before, details=0):
+    tu_total = 0
+    target = None
+    issue_time = None
+
+    for point in timeline:
+        issue_time = point['issue_time']
+        if issue_time <= after:
+            continue
+        if issue_time > before:
+            break
+
+        target = point['target']
+        tu = point['target_allocated_through']
+        tu_total += tu
+
+        if details:
+            yield  (target,
+                    point['resource'],
+                    point['name'],
+                    issue_time,
+                    tu,
+                    tu_total)
+
+    if not tu_total:
+        return
+
+    yield  (target,
+            'total',
+            point['resource'],
+            issue_time,
+            tu_total // len(timeline),
+            tu_total)
+
+
+def timeline_charge(entity, resource, after, before, details, charge_type):
+    key = '1'
+    if charge_type == 'charge_usage':
+        charge_units = usage_units
+    elif charge_type == 'charge_traffic':
+        charge_units = traffic_units
+    else:
+        m = 'charge type %s not supported' % charge_type
+        raise ValueError(m)
+
+    quotaholder = QuotaholderClient(QUOTA_HOLDER_URL)
+    timeline = quotaholder.get_timeline(
+        context={},
+        after=after,
+        before=before,
+        get_timeline=[[entity, resource, key]])
+    cu = charge_units(timeline, after, before, details=details)
+    return cu
diff --git a/snf-astakos-app/astakos/im/fixtures/groups.json b/snf-astakos-app/astakos/im/fixtures/groups.json
deleted file mode 100644 (file)
index 4b325cf..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-[
-    {
-        "model": "auth.group",
-        "pk": 1,
-        "fields": {
-            "name": "default"
-        }
-    },
-    {
-        "model": "auth.group",
-        "pk": 2,
-        "fields": {
-            "name": "academic"
-        }
-    },
-    {
-        "model": "auth.group",
-        "pk": 3,
-        "fields": {
-            "name": "shibboleth"
-        }
-    },
-    {
-        "model": "auth.group",
-        "pk": 4,
-        "fields": {
-            "name": "helpdesk"
-        }
-    }
-]
index f55775b..b3c8638 100644 (file)
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 from urlparse import urljoin
-from datetime import datetime
 
 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
 from django.utils.http import int_to_base36
 from django.core.urlresolvers import reverse
-from django.utils.functional import lazy
 from django.utils.safestring import mark_safe
-from django.contrib import messages
 from django.utils.encoding import smart_str
-from django.forms.models import fields_for_model
+from django.conf import settings
 
 from astakos.im.models import (
-    AstakosUser, Invitation, get_latest_terms,
-    EmailChange, PendingThirdPartyUser
+    AstakosUser, EmailChange, AstakosGroup, Invitation, GroupKind,
+    Resource, PendingThirdPartyUser, get_latest_terms, RESOURCE_SEPARATOR
 )
-from astakos.im.settings import (INVITATIONS_PER_LEVEL, DEFAULT_FROM_EMAIL,
-    BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY, DEFAULT_CONTACT_EMAIL,
-    RECAPTCHA_ENABLED, LOGGING_LEVEL, PASSWORD_RESET_EMAIL_SUBJECT,
-    NEWPASSWD_INVALIDATE_TOKEN
+from astakos.im.settings import (
+    INVITATIONS_PER_LEVEL, BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY,
+    RECAPTCHA_ENABLED, DEFAULT_CONTACT_EMAIL, LOGGING_LEVEL,
+    PASSWORD_RESET_EMAIL_SUBJECT, NEWPASSWD_INVALIDATE_TOKEN
 )
 from astakos.im.widgets import DummyWidget, RecaptchaWidget
 from astakos.im.functions import send_change_email
 
-# since Django 1.4 use django.core.urlresolvers.reverse_lazy instead
-from astakos.im.util import reverse_lazy, reserved_email, get_query
+from astakos.im.util import reserved_email, get_query
+
+import astakos.im.messages as astakos_messages
 
 import logging
 import hashlib
@@ -70,6 +69,7 @@ from random import random
 
 logger = logging.getLogger(__name__)
 
+
 class LocalUserCreationForm(UserCreationForm):
     """
     Extends the built in UserCreationForm in several ways:
@@ -79,11 +79,13 @@ class LocalUserCreationForm(UserCreationForm):
     * User created is not active.
     """
     recaptcha_challenge_field = forms.CharField(widget=DummyWidget)
-    recaptcha_response_field = forms.CharField(widget=RecaptchaWidget, label='')
+    recaptcha_response_field = forms.CharField(
+        widget=RecaptchaWidget, label='')
 
     class Meta:
         model = AstakosUser
-        fields = ("email", "first_name", "last_name", "has_signed_terms", "has_signed_terms")
+        fields = ("email", "first_name", "last_name",
+                  "has_signed_terms", "has_signed_terms")
 
     def __init__(self, *args, **kwargs):
         """
@@ -100,7 +102,7 @@ class LocalUserCreationForm(UserCreationForm):
 
         if RECAPTCHA_ENABLED:
             self.fields.keyOrder.extend(['recaptcha_challenge_field',
-                                         'recaptcha_response_field',])
+                                         'recaptcha_response_field', ])
         if get_latest_terms():
             self.fields.keyOrder.append('has_signed_terms')
 
@@ -108,22 +110,22 @@ class LocalUserCreationForm(UserCreationForm):
             # Overriding field label since we need to apply a link
             # to the terms within the label
             terms_link_html = '<a href="%s" target="_blank">%s</a>' \
-                    % (reverse('latest_terms'), _("the terms"))
+                % (reverse('latest_terms'), _("the terms"))
             self.fields['has_signed_terms'].label = \
-                    mark_safe("I agree with %s" % terms_link_html)
+                mark_safe("I agree with %s" % terms_link_html)
 
     def clean_email(self):
         email = self.cleaned_data['email']
         if not email:
-            raise forms.ValidationError(_("This field is required"))
+            raise forms.ValidationError(_(astakos_messages.REQUIRED_FIELD))
         if reserved_email(email):
-            raise forms.ValidationError(_("This email is already used"))
+            raise forms.ValidationError(_(astakos_messages.EMAIL_USED))
         return email
 
     def clean_has_signed_terms(self):
         has_signed_terms = self.cleaned_data['has_signed_terms']
         if not has_signed_terms:
-            raise forms.ValidationError(_('You have to agree with the terms'))
+            raise forms.ValidationError(_(astakos_messages.SIGN_TERMS))
         return has_signed_terms
 
     def clean_recaptcha_response_field(self):
@@ -141,7 +143,7 @@ class LocalUserCreationForm(UserCreationForm):
         rrf = self.cleaned_data['recaptcha_response_field']
         check = captcha.submit(rcf, rrf, RECAPTCHA_PRIVATE_KEY, self.ip)
         if not check.is_valid:
-            raise forms.ValidationError(_('You have not entered the correct words'))
+            raise forms.ValidationError(_(astakos_messages.CAPTCHA_VALIDATION_ERR))
 
     def save(self, commit=True):
         """
@@ -151,9 +153,10 @@ class LocalUserCreationForm(UserCreationForm):
         user = super(LocalUserCreationForm, self).save(commit=False)
         if commit:
             user.save()
-            logger._log(LOGGING_LEVEL, 'Created user %s' % user.email, [])
+            logger.log(LOGGING_LEVEL, 'Created user %s' % user.email)
         return user
 
+
 class InvitedLocalUserCreationForm(LocalUserCreationForm):
     """
     Extends the LocalUserCreationForm: email is readonly.
@@ -173,7 +176,6 @@ class InvitedLocalUserCreationForm(LocalUserCreationForm):
         for f in ro:
             self.fields[f].widget.attrs['readonly'] = True
 
-
     def save(self, commit=True):
         user = super(InvitedLocalUserCreationForm, self).save(commit=False)
         level = user.invitation.inviter.level + 1
@@ -184,6 +186,7 @@ class InvitedLocalUserCreationForm(LocalUserCreationForm):
             user.save()
         return user
 
+
 class ThirdPartyUserCreationForm(forms.ModelForm):
     id = forms.CharField(
         widget=forms.HiddenInput(),
@@ -219,20 +222,20 @@ class ThirdPartyUserCreationForm(forms.ModelForm):
             # Overriding field label since we need to apply a link
             # to the terms within the label
             terms_link_html = '<a href="%s" target="_blank">%s</a>' \
-                    % (reverse('latest_terms'), _("the terms"))
+                % (reverse('latest_terms'), _("the terms"))
             self.fields['has_signed_terms'].label = \
                     mark_safe("I agree with %s" % terms_link_html)
     
     def clean_email(self):
         email = self.cleaned_data['email']
         if not email:
-            raise forms.ValidationError(_("This field is required"))
+            raise forms.ValidationError(_(astakos_messages.REQUIRED_FIELD))
         return email
 
     def clean_has_signed_terms(self):
         has_signed_terms = self.cleaned_data['has_signed_terms']
         if not has_signed_terms:
-            raise forms.ValidationError(_('You have to agree with the terms'))
+            raise forms.ValidationError(_(astakos_messages.SIGN_TERMS))
         return has_signed_terms
 
     def save(self, commit=True):
@@ -241,9 +244,10 @@ class ThirdPartyUserCreationForm(forms.ModelForm):
         user.provider = get_query(self.request).get('provider')
         if commit:
             user.save()
-            logger._log(LOGGING_LEVEL, 'Created user %s' % user.email, [])
+            logger.log(LOGGING_LEVEL, 'Created user %s' % user.email)
         return user
 
+
 class InvitedThirdPartyUserCreationForm(ThirdPartyUserCreationForm):
     """
     Extends the ThirdPartyUserCreationForm: email is readonly.
@@ -252,7 +256,8 @@ class InvitedThirdPartyUserCreationForm(ThirdPartyUserCreationForm):
         """
         Changes the order of fields, and removes the username field.
         """
-        super(InvitedThirdPartyUserCreationForm, self).__init__(*args, **kwargs)
+        super(
+            InvitedThirdPartyUserCreationForm, self).__init__(*args, **kwargs)
 
         #set readonly form fields
         ro = ('email',)
@@ -260,7 +265,8 @@ class InvitedThirdPartyUserCreationForm(ThirdPartyUserCreationForm):
             self.fields[f].widget.attrs['readonly'] = True
 
     def save(self, commit=True):
-        user = super(InvitedThirdPartyUserCreationForm, self).save(commit=False)
+        user = super(
+            InvitedThirdPartyUserCreationForm, self).save(commit=False)
         level = user.invitation.inviter.level + 1
         user.level = level
         user.invitations = INVITATIONS_PER_LEVEL.get(level, 0)
@@ -269,8 +275,10 @@ class InvitedThirdPartyUserCreationForm(ThirdPartyUserCreationForm):
             user.save()
         return user
 
+
 class ShibbolethUserCreationForm(ThirdPartyUserCreationForm):
-    additional_email = forms.CharField(widget=forms.HiddenInput(), label='', required = False)
+    additional_email = forms.CharField(
+        widget=forms.HiddenInput(), label='', required=False)
 
     def __init__(self, *args, **kwargs):
         super(ShibbolethUserCreationForm, self).__init__(*args, **kwargs)
@@ -310,13 +318,17 @@ class ShibbolethUserCreationForm(ThirdPartyUserCreationForm):
             p.delete()
         return user
 
-class InvitedShibbolethUserCreationForm(ShibbolethUserCreationForm, InvitedThirdPartyUserCreationForm):
+
+class InvitedShibbolethUserCreationForm(ShibbolethUserCreationForm,
+                                        InvitedThirdPartyUserCreationForm):
     pass
 
+
 class LoginForm(AuthenticationForm):
     username = forms.EmailField(label=_("Email"))
     recaptcha_challenge_field = forms.CharField(widget=DummyWidget)
-    recaptcha_response_field = forms.CharField(widget=RecaptchaWidget, label='')
+    recaptcha_response_field = forms.CharField(
+        widget=RecaptchaWidget, label='')
 
     def __init__(self, *args, **kwargs):
         was_limited = kwargs.get('was_limited', False)
@@ -334,7 +346,11 @@ class LoginForm(AuthenticationForm):
         self.fields.keyOrder = ['username', 'password']
         if was_limited and RECAPTCHA_ENABLED:
             self.fields.keyOrder.extend(['recaptcha_challenge_field',
-                                         'recaptcha_response_field',])
+                                         'recaptcha_response_field', ])
+
+    def clean_username(self):
+        if 'username' in self.cleaned_data:
+            return self.cleaned_data['username'].lower()
 
     def clean_recaptcha_response_field(self):
         if 'recaptcha_challenge_field' in self.cleaned_data:
@@ -351,7 +367,7 @@ class LoginForm(AuthenticationForm):
         rrf = self.cleaned_data['recaptcha_response_field']
         check = captcha.submit(rcf, rrf, RECAPTCHA_PRIVATE_KEY, self.ip)
         if not check.is_valid:
-            raise forms.ValidationError(_('You have not entered the correct words'))
+            raise forms.ValidationError(_(astakos_messages.CAPTCHA_VALIDATION_ERR))
     
     def clean(self):
         """
@@ -367,19 +383,22 @@ class LoginForm(AuthenticationForm):
                     raise
         return self.cleaned_data
 
+
 class ProfileForm(forms.ModelForm):
     """
     Subclass of ``ModelForm`` for permiting user to edit his/her profile.
-    Most of the fields are readonly since the user is not allowed to change them.
+    Most of the fields are readonly since the user is not allowed to change
+    them.
 
-    The class defines a save method which sets ``is_verified`` to True so as the user
-    during the next login will not to be redirected to profile page.
+    The class defines a save method which sets ``is_verified`` to True so as the
+    user during the next login will not to be redirected to profile page.
     """
     renew = forms.BooleanField(label='Renew token', required=False)
 
     class Meta:
         model = AstakosUser
-        fields = ('email', 'first_name', 'last_name', 'auth_token', 'auth_token_expires')
+        fields = ('email', 'first_name', 'last_name', 'auth_token',
+                  'auth_token_expires')
 
     def __init__(self, *args, **kwargs):
         self.session_key = kwargs.pop('session_key', None)
@@ -402,6 +421,7 @@ class ProfileForm(forms.ModelForm):
             user.save()
         return user
 
+
 class FeedbackForm(forms.Form):
     """
     Form for writing feedback.
@@ -410,14 +430,16 @@ class FeedbackForm(forms.Form):
     feedback_data = forms.CharField(widget=forms.HiddenInput(), label='',
                                     required=False)
 
+
 class SendInvitationForm(forms.Form):
     """
     Form for sending an invitations
     """
 
-    email = forms.EmailField(required = True, label = 'Email address')
-    first_name = forms.EmailField(label = 'First name')
-    last_name = forms.EmailField(label = 'Last name')
+    email = forms.EmailField(required=True, label='Email address')
+    first_name = forms.EmailField(label='First name')
+    last_name = forms.EmailField(label='Last name')
+
 
 class ExtendedPasswordResetForm(PasswordResetForm):
     """
@@ -432,20 +454,23 @@ class ExtendedPasswordResetForm(PasswordResetForm):
         try:
             user = AstakosUser.objects.get(email=email, is_active=True)
             if not user.has_usable_password():
-                raise forms.ValidationError(_("This account has not a usable password."))
-        except AstakosUser.DoesNotExist, e:
-            raise forms.ValidationError(_('That e-mail address doesn\'t have an associated user account. Are you sure you\'ve registered?'))
+                raise forms.ValidationError(_(astakos_messages.UNUSABLE_PASSWORD))
+        except AstakosUser.DoesNotExist:
+            raise forms.ValidationError(_(astakos_messages.EMAIL_UNKNOWN))
         return email
 
-    def save(self, domain_override=None, email_template_name='registration/password_reset_email.html',
-             use_https=False, token_generator=default_token_generator, request=None):
+    def save(
+        self, domain_override=None, email_template_name='registration/password_reset_email.html',
+            use_https=False, token_generator=default_token_generator, request=None):
         """
         Generates a one-use only link for resetting password and sends to the user.
         """
         for user in self.users_cache:
             url = reverse('django.contrib.auth.views.password_reset_confirm',
-                          kwargs={'uidb36':int_to_base36(user.id),
-                                  'token':token_generator.make_token(user)})
+                          kwargs={'uidb36': int_to_base36(user.id),
+                                  'token': token_generator.make_token(user)
+                                  }
+                          )
             url = urljoin(BASEURL, url)
             t = loader.get_template(email_template_name)
             c = {
@@ -456,9 +481,10 @@ class ExtendedPasswordResetForm(PasswordResetForm):
                 'baseurl': BASEURL,
                 'support': DEFAULT_CONTACT_EMAIL
             }
-            from_email = DEFAULT_FROM_EMAIL
+            from_email = settings.SERVER_EMAIL
             send_mail(_(PASSWORD_RESET_EMAIL_SUBJECT),
-                t.render(Context(c)), from_email, [user.email])
+                      t.render(Context(c)), from_email, [user.email])
+
 
 class EmailChangeForm(forms.ModelForm):
     class Meta:
@@ -468,18 +494,20 @@ class EmailChangeForm(forms.ModelForm):
     def clean_new_email_address(self):
         addr = self.cleaned_data['new_email_address']
         if AstakosUser.objects.filter(email__iexact=addr):
-            raise forms.ValidationError(_(u'This email address is already in use. Please supply a different email address.'))
+            raise forms.ValidationError(_(astakos_messages.EMAIL_USED))
         return addr
 
     def save(self, email_template_name, request, commit=True):
         ec = super(EmailChangeForm, self).save(commit=False)
         ec.user = request.user
-        activation_key = hashlib.sha1(str(random()) + smart_str(ec.new_email_address))
-        ec.activation_key=activation_key.hexdigest()
+        activation_key = hashlib.sha1(
+            str(random()) + smart_str(ec.new_email_address))
+        ec.activation_key = activation_key.hexdigest()
         if commit:
             ec.save()
         send_change_email(ec, request, email_template_name=email_template_name)
 
+
 class SignApprovalTermsForm(forms.ModelForm):
     class Meta:
         model = AstakosUser
@@ -491,9 +519,10 @@ class SignApprovalTermsForm(forms.ModelForm):
     def clean_has_signed_terms(self):
         has_signed_terms = self.cleaned_data['has_signed_terms']
         if not has_signed_terms:
-            raise forms.ValidationError(_('You have to agree with the terms'))
+            raise forms.ValidationError(_(astakos_messages.SIGN_TERMS))
         return has_signed_terms
 
+
 class InvitationForm(forms.ModelForm):
     username = forms.EmailField(label=_("Email"))
 
@@ -507,12 +536,13 @@ class InvitationForm(forms.ModelForm):
     def clean_username(self):
         username = self.cleaned_data['username']
         try:
-            Invitation.objects.get(username = username)
-            raise forms.ValidationError(_('There is already invitation for this email.'))
+            Invitation.objects.get(username=username)
+            raise forms.ValidationError(_(astakos_messages.INVITATION_EMAIL_EXISTS))
         except Invitation.DoesNotExist:
             pass
         return username
 
+
 class ExtendedPasswordChangeForm(PasswordChangeForm):
     """
     Extends PasswordChangeForm by enabling user
@@ -537,6 +567,260 @@ class ExtendedPasswordChangeForm(PasswordChangeForm):
             pass
         return super(ExtendedPasswordChangeForm, self).save(commit=commit)
 
+
+class AstakosGroupCreationForm(forms.ModelForm):
+    kind = forms.ModelChoiceField(
+        queryset=GroupKind.objects.all(),
+        label="",
+        widget=forms.HiddenInput()
+    )
+    name = forms.URLField(widget=forms.TextInput(attrs={'placeholder': 'eg. foo.ece.ntua.gr'}), help_text="Name should be in the form of dns",)
+    moderation_enabled = forms.BooleanField(
+        help_text="Check if you want to approve members participation manually",
+        required=False,
+        initial=True
+    )
+    max_participants = forms.IntegerField(
+        required=True, min_value=1
+    )
+
+    class Meta:
+        model = AstakosGroup
+
+    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)
+#         max_participants = None if members_unlimited else members_uplimit
+#         qd['max_participants']= max_participants.pop(0) if max_participants else 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 policies(self):
+        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
+
+class AstakosGroupCreationSummaryForm(forms.ModelForm):
+    kind = forms.ModelChoiceField(
+        queryset=GroupKind.objects.all(),
+        label="",
+        widget=forms.HiddenInput()
+    )
+    name = forms.URLField()
+    moderation_enabled = forms.BooleanField(
+        help_text="Check if you want to approve members participation manually",
+        required=False,
+        initial=True
+    )
+    max_participants = forms.IntegerField(
+        required=False, min_value=1
+    )
+
+    class Meta:
+        model = AstakosGroup
+
+    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)
+#         max_participants = None if members_unlimited else members_uplimit
+#         qd['max_participants']= max_participants.pop(0) if max_participants else None
+        
+        #substitue QueryDict
+        args.insert(0, qd)
+        
+        super(AstakosGroupCreationSummaryForm, self).__init__(*args, **kwargs)
+        self.fields.keyOrder = ['kind', 'name', 'homepage', 'desc',
+                                'issue_date', 'expiration_date',
+                                'moderation_enabled', 'max_participants']
+        def add_fields((k, v)):
+            self.fields[k] = forms.IntegerField(
+                required=False,
+                widget=forms.TextInput(),
+                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_'))
+        )
+        for f in self.fields.values():
+            f.widget = forms.HiddenInput()
+
+    def clean(self):
+        super(AstakosGroupCreationSummaryForm, self).clean()
+        self.cleaned_data['policies'] = []
+        append = self.cleaned_data['policies'].append
+        #tbd = [f for f in self.fields if (f.startswith('is_selected_') and (not f.endswith('_proxy')))]
+        tbd = [f for f in self.fields if f.startswith('is_selected_')]
+        for name, uplimit in self.cleaned_data.iteritems():
+            subs = name.split('_uplimit')
+            if len(subs) == 2:
+                tbd.append(name)
+                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))
+        for name in tbd:
+            self.cleaned_data.pop(name, None)
+        return self.cleaned_data
+
+class AstakosGroupUpdateForm(forms.ModelForm):
+    class Meta:
+        model = AstakosGroup
+        fields = ('homepage', 'desc')
+
+
+class AddGroupMembersForm(forms.Form):
+    q = forms.CharField(
+        max_length=800, widget=forms.Textarea, label=_('Add members'),
+        help_text=_(astakos_messages.ADD_GROUP_MEMBERS_Q_HELP),
+        required=True)
+
+    def clean(self):
+        q = self.cleaned_data.get('q') or ''
+        users = q.split(',')
+        users = list(u.strip() for u in users if u)
+        db_entries = AstakosUser.objects.filter(email__in=users)
+        unknown = list(set(users) - set(u.email for u in db_entries))
+        if unknown:
+            raise forms.ValidationError(_(astakos_messages.UNKNOWN_USERS) % ','.join(unknown))
+        self.valid_users = db_entries
+        return self.cleaned_data
+
+    def get_valid_users(self):
+        """Should be called after form cleaning"""
+        try:
+            return self.valid_users
+        except:
+            return ()
+
+
+class AstakosGroupSearchForm(forms.Form):
+    q = forms.CharField(max_length=200, label='Search group')
+
+
+class TimelineForm(forms.Form):
+    entity = forms.ModelChoiceField(
+        queryset=AstakosUser.objects.filter(is_active=True)
+    )
+    resource = forms.ModelChoiceField(
+        queryset=Resource.objects.all()
+    )
+    start_date = forms.DateTimeField()
+    end_date = forms.DateTimeField()
+    details = forms.BooleanField(required=False, label="Detailed Listing")
+    operation = forms.ChoiceField(
+        label='Charge Method',
+        choices=(('', '-------------'),
+                 ('charge_usage', 'Charge Usage'),
+                 ('charge_traffic', 'Charge Traffic'), )
+    )
+
+    def clean(self):
+        super(TimelineForm, self).clean()
+        d = self.cleaned_data
+        if 'resource' in d:
+            d['resource'] = str(d['resource'])
+        if 'start_date' in d:
+            d['start_date'] = d['start_date'].strftime(
+                "%Y-%m-%dT%H:%M:%S.%f")[:24]
+        if 'end_date' in d:
+            d['end_date'] = d['end_date'].strftime("%Y-%m-%dT%H:%M:%S.%f")[:24]
+        if 'entity' in d:
+            d['entity'] = d['entity'].email
+        return d
+
+
+class AstakosGroupSortForm(forms.Form):
+    sort_by = forms.ChoiceField(label='Sort by',
+                                choices=(('groupname', 'Name'),
+                                         ('kindname', 'Type'),
+                                         ('issue_date', 'Issue Date'),
+                                         ('expiration_date',
+                                          'Expiration Date'),
+                                         ('approved_members_num',
+                                          'Participants'),
+                                         ('is_enabled', 'Status'),
+                                         ('moderation_enabled', 'Moderation'),
+                                         ('membership_status',
+                                          'Enrollment Status')
+                                         ),
+                                required=False)
+
+
+class MembersSortForm(forms.Form):
+    sort_by = forms.ChoiceField(label='Sort by',
+                                choices=(('person__email', 'User Id'),
+                                         ('person__first_name', 'Name'),
+                                         ('date_joined', 'Status')
+                                         ),
+                                required=False)
+
+
+class PickResourceForm(forms.Form):
+    resource = forms.ModelChoiceField(
+        queryset=Resource.objects.select_related().all()
+    )
+    resource.widget.attrs["onchange"] = "this.form.submit()"
+
+
 class ExtendedSetPasswordForm(SetPasswordForm):
     """
     Extends SetPasswordForm by enabling user
@@ -552,7 +836,7 @@ class ExtendedSetPasswordForm(SetPasswordForm):
     
     def __init__(self, user, *args, **kwargs):
         super(ExtendedSetPasswordForm, self).__init__(user, *args, **kwargs)
-    
+
     def save(self, commit=True):
         try:
             self.user = AstakosUser.objects.get(id=self.user.id)
@@ -561,5 +845,4 @@ class ExtendedSetPasswordForm(SetPasswordForm):
             self.user.flush_sessions()
         except BaseException, e:
             logger.exception(e)
-            pass
         return super(ExtendedSetPasswordForm, self).save(commit=commit)
index 192bf77..d906893 100644 (file)
@@ -1,18 +1,18 @@
 # Copyright 2011 GRNET S.A. All rights reserved.
-# 
+#
 # Redistribution and use in source and binary forms, with or
 # without modification, are permitted provided that the following
 # conditions are met:
-# 
+#
 #   1. Redistributions of source code must retain the above
 #      copyright notice, this list of conditions and the following
 #      disclaimer.
-# 
+#
 #   2. Redistributions in binary form must reproduce the above
 #      copyright notice, this list of conditions and the following
 #      disclaimer in the documentation and/or other materials
 #      provided with the distribution.
-# 
+#
 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
@@ -25,7 +25,7 @@
 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 # POSSIBILITY OF SUCH DAMAGE.
-# 
+#
 # The views and conclusions contained in the software and
 # documentation are those of the authors and should not be
 # interpreted as representing official policies, either expressed
@@ -38,14 +38,13 @@ from django.utils.translation import ugettext as _
 from django.template.loader import render_to_string
 from django.core.mail import send_mail
 from django.core.urlresolvers import reverse
-from django.core.exceptions import ValidationError
 from django.template import Context, loader
 from django.contrib.auth import (
     login as auth_login,
-    logout as auth_logout,
-    SESSION_KEY
+    logout as auth_logout
 )
-from django.http import HttpRequest
+from django.conf import settings
+from django.contrib.auth.models import AnonymousUser
 
 from urllib import quote
 from urlparse import urljoin
@@ -54,57 +53,61 @@ from datetime import datetime
 from functools import wraps
 
 from astakos.im.settings import (
-    DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL,
-    SITENAME, BASEURL, DEFAULT_ADMIN_EMAIL, LOGGING_LEVEL,
-    VERIFICATION_EMAIL_SUBJECT, ADMIN_NOTIFICATION_EMAIL_SUBJECT,
-    HELPDESK_NOTIFICATION_EMAIL_SUBJECT, INVITATION_EMAIL_SUBJECT,
-    GREETING_EMAIL_SUBJECT, FEEDBACK_EMAIL_SUBJECT, EMAIL_CHANGE_EMAIL_SUBJECT
+    DEFAULT_CONTACT_EMAIL, SITENAME, BASEURL, LOGGING_LEVEL,
+    VERIFICATION_EMAIL_SUBJECT, ACCOUNT_CREATION_SUBJECT,
+    GROUP_CREATION_SUBJECT, HELPDESK_NOTIFICATION_EMAIL_SUBJECT,
+    INVITATION_EMAIL_SUBJECT, GREETING_EMAIL_SUBJECT, FEEDBACK_EMAIL_SUBJECT,
+    EMAIL_CHANGE_EMAIL_SUBJECT
 )
-from astakos.im.models import Invitation, AstakosUser, SessionCatalog
+import astakos.im.messages as astakos_messages
+import astakos.im.models
 
 logger = logging.getLogger(__name__)
 
+
 def logged(func, msg):
     @wraps(func)
     def with_logging(*args, **kwargs):
         email = ''
         user = None
-        if len(args) == 2 and isinstance(args[1], AstakosUser):
-            user = args[1]
-        elif len(args) == 1 and isinstance(args[0], HttpRequest):
+        try:
             request = args[0]
-            user = request.user
-        email = user.email if user and user.is_authenticated() else ''
+            email = request.user.email
+        except (KeyError, AttributeError), e:
+            email = ''
         r = func(*args, **kwargs)
         if LOGGING_LEVEL:
-            logger._log(LOGGING_LEVEL, msg % email, [])
+            logger.log(LOGGING_LEVEL, msg % email)
         return r
     return with_logging
 
 
 def login(request, user):
     auth_login(request, user)
-    SessionCatalog(session_key=request.session.session_key, user=user).save()
+    astakos.im.models.SessionCatalog(
+        session_key=request.session.session_key,
+        user=user).save()
 
 login = logged(login, '%s logged in.')
 logout = logged(auth_logout, '%s logged out.')
 
+
 def send_verification(user, template_name='im/activation_email.txt'):
     """
     Send email to user to verify his/her email and activate his/her account.
-    
+
     Raises SendVerificationError
     """
-    url = '%s?auth=%s&next=%s' % (urljoin(BASEURL, reverse('astakos.im.views.activate')),
-                                    quote(user.auth_token),
-                                    quote(urljoin(BASEURL, reverse('astakos.im.views.index'))))
+    url = '%s?auth=%s&next=%s' % (urljoin(BASEURL, reverse('activate')),
+                                  quote(user.auth_token),
+                                  quote(urljoin(BASEURL, reverse('index'))))
     message = render_to_string(template_name, {
-            'user': user,
-            'url': url,
-            'baseurl': BASEURL,
-            'site_name': SITENAME,
-            'support': DEFAULT_CONTACT_EMAIL})
-    sender = DEFAULT_FROM_EMAIL
+                               'user': user,
+                               'url': url,
+                               'baseurl': BASEURL,
+                               'site_name': SITENAME,
+                               'support': DEFAULT_CONTACT_EMAIL})
+    sender = settings.SERVER_EMAIL
     try:
         send_mail(_(VERIFICATION_EMAIL_SUBJECT), message, sender, [user.email])
     except (SMTPException, socket.error) as e:
@@ -112,74 +115,91 @@ def send_verification(user, template_name='im/activation_email.txt'):
         raise SendVerificationError()
     else:
         msg = 'Sent activation %s' % user.email
-        logger._log(LOGGING_LEVEL, msg, [])
+        logger.log(LOGGING_LEVEL, msg)
+
 
 def send_activation(user, template_name='im/activation_email.txt'):
     send_verification(user, template_name)
     user.activation_sent = datetime.now()
     user.save()
 
-def send_admin_notification(user, template_name='im/admin_notification.txt'):
+
+def _send_admin_notification(template_name,
+                             dictionary=None,
+                             subject='alpha2 testing notification',):
     """
-    Send email to DEFAULT_ADMIN_EMAIL to notify for a new user registration.
-    
+    Send notification email to settings.ADMINS.
+
     Raises SendNotificationError
     """
-    if not DEFAULT_ADMIN_EMAIL:
+    if not settings.ADMINS:
         return
-    message = render_to_string(template_name, {
-            'user': user,
-            'baseurl': BASEURL,
-            'site_name': SITENAME,
-            'support': DEFAULT_CONTACT_EMAIL})
-    sender = DEFAULT_FROM_EMAIL
+    dictionary = dictionary or {}
+    message = render_to_string(template_name, dictionary)
+    sender = settings.SERVER_EMAIL
     try:
-        send_mail(_(ADMIN_NOTIFICATION_EMAIL_SUBJECT) % {'user': user.email}, message, sender, [DEFAULT_ADMIN_EMAIL])
+        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' % user.email
-        logger._log(LOGGING_LEVEL, msg, [])
+        msg = 'Sent admin notification for user %s' % dictionary
+        logger.log(LOGGING_LEVEL, msg)
+
+
+def send_account_creation_notification(template_name, dictionary=None):
+    user = dictionary.get('user', AnonymousUser())
+    subject = _(ACCOUNT_CREATION_SUBJECT) % {'user':user.get('email', '')}
+    return _send_admin_notification(template_name, dictionary, subject=subject)
+
 
-def send_helpdesk_notification(user, template_name='im/helpdesk_notification.txt'):
+def send_group_creation_notification(template_name, dictionary=None):
+    group = dictionary.get('group', astakos.im.models.AstakosGroup())
+    subject = _(GROUP_CREATION_SUBJECT) % {'group':group.get('name', '')}
+    return _send_admin_notification(template_name, dictionary, subject=subject)
+
+
+def send_helpdesk_notification(user, template_name='im/account_notification.txt'):
     """
     Send email to DEFAULT_CONTACT_EMAIL to notify for a new user activation.
-    
+
     Raises SendNotificationError
     """
     if not DEFAULT_CONTACT_EMAIL:
         return
-    message = render_to_string(template_name, {
-            'user': user,
-            'baseurl': BASEURL,
-            'site_name': SITENAME,
-            'support': DEFAULT_ADMIN_EMAIL})
-    sender = DEFAULT_FROM_EMAIL
+    message = render_to_string(
+        template_name,
+        {'user': user}
+    )
+    sender = settings.SERVER_EMAIL
     try:
-        send_mail(_(HELPDESK_NOTIFICATION_EMAIL_SUBJECT) % {'user': user.email}, message, sender, [DEFAULT_CONTACT_EMAIL])
+        send_mail(
+            _(HELPDESK_NOTIFICATION_EMAIL_SUBJECT) % {'user': user.email},
+            message, sender, [DEFAULT_CONTACT_EMAIL])
     except (SMTPException, socket.error) as e:
         logger.exception(e)
         raise SendNotificationError()
     else:
-        msg = 'Sent helpdesk admin notification for user %s' % user.email
-        logger._log(LOGGING_LEVEL, msg, [])
+        msg = 'Sent helpdesk admin notification for %s' % user.email
+        logger.log(LOGGING_LEVEL, msg)
+
 
 def send_invitation(invitation, template_name='im/invitation.txt'):
     """
     Send invitation email.
-    
+
     Raises SendInvitationError
     """
     subject = _(INVITATION_EMAIL_SUBJECT)
-    url = '%s?code=%d' % (urljoin(BASEURL, reverse('astakos.im.views.index')), invitation.code)
-    message = render_to_string('im/invitation.txt', {
-                'invitation': invitation,
-                'url': url,
-                'baseurl': BASEURL,
-                'site_name': SITENAME,
-                'support': DEFAULT_CONTACT_EMAIL})
-    sender = DEFAULT_FROM_EMAIL
+    url = '%s?code=%d' % (urljoin(BASEURL, reverse('index')), invitation.code)
+    message = render_to_string(template_name, {
+                               'invitation': invitation,
+                               'url': url,
+                               'baseurl': BASEURL,
+                               'site_name': SITENAME,
+                               'support': DEFAULT_CONTACT_EMAIL})
+    sender = settings.SERVER_EMAIL
     try:
         send_mail(subject, message, sender, [invitation.username])
     except (SMTPException, socket.error) as e:
@@ -187,22 +207,25 @@ def send_invitation(invitation, template_name='im/invitation.txt'):
         raise SendInvitationError()
     else:
         msg = 'Sent invitation %s' % invitation
-        logger._log(LOGGING_LEVEL, msg, [])
+        logger.log(LOGGING_LEVEL, msg)
+        invitation.inviter.invitations = max(0, invitation.inviter.invitations - 1)
+        invitation.inviter.save()
+
 
 def send_greeting(user, email_template_name='im/welcome_email.txt'):
     """
     Send welcome email.
-    
+
     Raises SMTPException, socket.error
     """
     subject = _(GREETING_EMAIL_SUBJECT)
     message = render_to_string(email_template_name, {
-                'user': user,
-                'url': urljoin(BASEURL, reverse('astakos.im.views.index')),
-                'baseurl': BASEURL,
-                'site_name': SITENAME,
-                'support': DEFAULT_CONTACT_EMAIL})
-    sender = DEFAULT_FROM_EMAIL
+                               'user': user,
+                               'url': urljoin(BASEURL, reverse('index')),
+                               'baseurl': BASEURL,
+                               'site_name': SITENAME,
+                               'support': DEFAULT_CONTACT_EMAIL})
+    sender = settings.SERVER_EMAIL
     try:
         send_mail(subject, message, sender, [user.email])
     except (SMTPException, socket.error) as e:
@@ -210,7 +233,8 @@ def send_greeting(user, email_template_name='im/welcome_email.txt'):
         raise SendGreetingError()
     else:
         msg = 'Sent greeting %s' % user.email
-        logger._log(LOGGING_LEVEL, msg, [])
+        logger.log(LOGGING_LEVEL, msg)
+
 
 def send_feedback(msg, data, user, email_template_name='im/feedback_mail.txt'):
     subject = _(FEEDBACK_EMAIL_SUBJECT)
@@ -227,30 +251,32 @@ def send_feedback(msg, data, user, email_template_name='im/feedback_mail.txt'):
         raise SendFeedbackError()
     else:
         msg = 'Sent feedback from %s' % user.email
-        logger._log(LOGGING_LEVEL, msg, [])
+        logger.log(LOGGING_LEVEL, msg)
+
 
 def send_change_email(ec, request, email_template_name='registration/email_change_email.txt'):
     try:
         url = reverse('email_change_confirm',
-                      kwargs={'activation_key':ec.activation_key})
+                      kwargs={'activation_key': ec.activation_key})
         url = request.build_absolute_uri(url)
         t = loader.get_template(email_template_name)
         c = {'url': url, 'site_name': SITENAME}
-        from_email = DEFAULT_FROM_EMAIL
+        from_email = settings.SERVER_EMAIL
         send_mail(_(EMAIL_CHANGE_EMAIL_SUBJECT),
-            t.render(Context(c)), from_email, [ec.new_email_address])
+                  t.render(Context(c)), from_email, [ec.new_email_address])
     except (SMTPException, socket.error) as e:
         logger.exception(e)
         raise ChangeEmailError()
     else:
         msg = 'Sent change email for %s' % ec.user.email
-        logger._log(LOGGING_LEVEL, msg, [])
+        logger.log(LOGGING_LEVEL, msg)
+
 
 def activate(user, email_template_name='im/welcome_email.txt',
-                helpdesk_email_template_name='im/helpdesk_notification.txt', verify_email=False):
+             helpdesk_email_template_name='im/helpdesk_notification.txt', verify_email=False):
     """
     Activates the specific user and sends email.
-    
+
     Raises SendGreetingError, ValidationError
     """
     user.is_active = True
@@ -260,62 +286,65 @@ def activate(user, email_template_name='im/welcome_email.txt',
     send_helpdesk_notification(user, helpdesk_email_template_name)
     send_greeting(user, email_template_name)
 
-def invite(invitation, inviter, email_template_name='im/welcome_email.txt'):
-    """
-    Send an invitation email and upon success reduces inviter's invitation by one.
-    
-    Raises SendInvitationError
-    """
-    invitation.inviter = inviter
-    invitation.save()
-    send_invitation(invitation, email_template_name)
-    inviter.invitations = max(0, inviter.invitations - 1)
-    inviter.save()
 
-def set_user_credibility(email, has_credits):
+def switch_account_to_shibboleth(user, local_user,
+                                 greeting_template_name='im/welcome_email.txt'):
     try:
-        user = AstakosUser.objects.get(email=email, is_active=True)
-        user.has_credits = has_credits
-        user.save()
-    except AstakosUser.DoesNotExist, e:
-        logger.exception(e)
-    except ValidationError, e:
-        logger.exception(e)
+        provider = user.provider
+    except AttributeError:
+        return
+    else:
+        if not provider == 'shibboleth':
+            return
+        user.delete()
+        local_user.provider = 'shibboleth'
+        local_user.third_party_identifier = user.third_party_identifier
+        local_user.save()
+        send_greeting(local_user, greeting_template_name)
+        return local_user
+
 
 class SendMailError(Exception):
     pass
 
+
 class SendAdminNotificationError(SendMailError):
     def __init__(self):
-        self.message = _('Failed to send notification')
+        self.message = _(astakos_messages.ADMIN_NOTIFICATION_SEND_ERR)
         super(SendAdminNotificationError, self).__init__()
 
+
 class SendVerificationError(SendMailError):
     def __init__(self):
-        self.message = _('Failed to send verification')
+        self.message = _(astakos_messages.VERIFICATION_SEND_ERR)
         super(SendVerificationError, self).__init__()
 
+
 class SendInvitationError(SendMailError):
     def __init__(self):
-        self.message = _('Failed to send invitation')
+        self.message = _(astakos_messages.INVITATION_SEND_ERR)
         super(SendInvitationError, self).__init__()
 
+
 class SendGreetingError(SendMailError):
     def __init__(self):
-        self.message = _('Failed to send greeting')
+        self.message = _(astakos_messages.GREETING_SEND_ERR)
         super(SendGreetingError, self).__init__()
 
+
 class SendFeedbackError(SendMailError):
     def __init__(self):
-        self.message = _('Failed to send feedback')
+        self.message = _(astakos_messages.FEEDBACK_SEND_ERR)
         super(SendFeedbackError, self).__init__()
 
+
 class ChangeEmailError(SendMailError):
     def __init__(self):
-        self.message = _('Failed to send change email')
+        self.message = self.message = _(astakos_messages.CHANGE_EMAIL_SEND_ERR)
         super(ChangeEmailError, self).__init__()
 
+
 class SendNotificationError(SendMailError):
     def __init__(self):
-        self.message = _('Failed to send notification email')
+        self.message = _(astakos_messages.NOTIFICATION_SEND_ERR)
         super(SendNotificationError, self).__init__()
index 0771989..63a3542 100644 (file)
@@ -39,7 +39,8 @@ from django.contrib.contenttypes.models import ContentType
 
 from astakos.im.models import AstakosUser
 
-content_type = None
+DEFAULT_CONTENT_TYPE = None
+
 
 def get_user(email_or_id, **kwargs):
     try:
@@ -50,6 +51,7 @@ def get_user(email_or_id, **kwargs):
     except AstakosUser.DoesNotExist, AstakosUser.MultipleObjectsReturned:
         return None
 
+
 def format_bool(b):
     return 'YES' if b else 'NO'
 
@@ -63,16 +65,15 @@ def format_date(d):
     else:
         return 'in ' + timeuntil(d)
 
+
 def get_astakosuser_content_type():
-    if content_type:
-        return content_type
-    
     try:
         return ContentType.objects.get(app_label='im',
                                        model='astakosuser')
     except:
-        return content_type
-    
+        return DEFAULT_CONTENT_TYPE
+
+
 def add_user_permission(user, pname):
     content_type = get_astakosuser_content_type()
     if user.has_perm(pname):
@@ -83,6 +84,7 @@ def add_user_permission(user, pname):
     user.user_permissions.add(p)
     return 1, created
 
+
 def add_group_permission(group, pname):
     content_type = get_astakosuser_content_type()
     if pname in [p.codename for p in group.permissions.all()]:
@@ -95,26 +97,28 @@ def add_group_permission(group, pname):
     group.permissions.add(p)
     return 1, created
 
+
 def remove_user_permission(user, pname):
     content_type = get_astakosuser_content_type()
     if user.has_perm(pname):
         return 0
     try:
         p = Permission.objects.get(codename=pname,
-                                    content_type=content_type)
+                                   content_type=content_type)
         user.user_permissions.remove(p)
         return 1
-    except Permission.DoesNotExist, e:
+    except Permission.DoesNotExist:
         return -1
 
+
 def remove_group_permission(group, pname):
     content_type = get_astakosuser_content_type()
     if pname not in [p.codename for p in group.permissions.all()]:
         return 0
     try:
         p = Permission.objects.get(codename=pname,
-                                    content_type=content_type)
+                                   content_type=content_type)
         group.permissions.remove(p)
         return 1
-    except Permission.DoesNotExist, e:
-        return -1
\ No newline at end of file
+    except Permission.DoesNotExist:
+        return -1
index 25620da..6b35989 100644 (file)
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
-from optparse import make_option
-from random import choice
-from string import digits, lowercase, uppercase
-from uuid import uuid4
-from time import time
-from os.path import abspath
-
 from django.core.management.base import BaseCommand, CommandError
-from django.contrib.auth.models import Group
+
+from astakos.im.models import AstakosGroup
 
 from ._common import add_group_permission
 
+
 class Command(BaseCommand):
     args = "<groupname> [<permission> ...]"
     help = "Insert group"
-    
+
     def handle(self, *args, **options):
         if len(args) < 1:
             raise CommandError("Invalid number of arguments")
-        
+
         name = args[0].decode('utf8')
-        
+
         try:
-            Group.objects.get(name=name)
+            AstakosGroup.objects.get(name=name)
             raise CommandError("A group with this name already exists")
-        except Group.DoesNotExist, e:
-            group = Group(name=name)
+        except AstakosGroup.DoesNotExist, e:
+            group = AstakosGroup(name=name)
             group.save()
             msg = "Created group id %d" % (group.id,)
             self.stdout.write(msg + '\n')
@@ -65,10 +60,13 @@ class Command(BaseCommand):
                 for pname in args[1:]:
                     r, created = add_group_permission(group, pname)
                     if created:
-                        self.stdout.write('Permission: %s created successfully\n' % pname)
+                        self.stdout.write(
+                            'Permission: %s created successfully\n' % pname)
                     if r == 0:
-                        self.stdout.write('Group has already permission: %s\n' % pname)
+                        self.stdout.write(
+                            'Group has already permission: %s\n' % pname)
                     else:
-                        self.stdout.write('Permission: %s added successfully\n' % pname)
+                        self.stdout.write(
+                            'Permission: %s added successfully\n' % pname)
             except Exception, e:
-                raise CommandError(e)
\ No newline at end of file
+                raise CommandError(e)
index 68f401f..fbcbdff 100644 (file)
 
 from optparse import make_option
 
-from django.core.management.base import BaseCommand, CommandError
-from django.contrib.auth.models import Group
+from django.core.management.base import NoArgsCommand
 
-from astakos.im.models import AstakosUser
+from astakos.im.models import AstakosGroup
 
 from ._common import format_bool
 
 
-class Command(BaseCommand):
+class Command(NoArgsCommand):
     help = "List groups"
     
-    option_list = BaseCommand.option_list + (
+    option_list = NoArgsCommand.option_list + (
         make_option('-c',
-            action='store_true',
-            dest='csv',
-            default=False,
-            help="Use pipes to separate values"),
+                    action='store_true',
+                    dest='csv',
+                    default=False,
+                    help="Use pipes to separate values"),
+        make_option('-p',
+                    action='store_true',
+                    dest='pending',
+                    default=False,
+                    help="List only groups pending enable"),
     )
-    
-    def handle(self, *args, **options):
-        if args:
-            raise CommandError("Command doesn't accept any arguments")
-        
-        groups = Group.objects.all().order_by('id')
-        
-        labels = ('id', 'name', 'permissions')
-        columns = (3, 12, 50)
-        
-        if not options['csv']:
+
+    def handle_noargs(self, **options):
+        groups = AstakosGroup.objects.all()
+
+        if options.get('pending'):
+            groups = filter(lambda g: g.is_disabled, groups)
+
+        labels = ('id', 'name', 'enabled', 'moderation', 'permissions')
+        columns = (3, 25, 12, 12, 50)
+
+        if not options.get('csv'):
             line = ' '.join(l.rjust(w) for l, w in zip(labels, columns))
             self.stdout.write(line + '\n')
             sep = '-' * len(line)
             self.stdout.write(sep + '\n')
-        
+
         for group in groups:
-            fields = (str(group.id), group.name,
+            fields = (str(group.id),
+                      group.name,
+                      format_bool(group.is_enabled),
+                      format_bool(group.moderation_enabled),
                       ','.join(p.codename for p in group.permissions.all()))
-            
-            if options['csv']:
+
+            if options.get('csv'):
                 line = '|'.join(fields)
             else:
                 line = ' '.join(f.rjust(w) for f, w in zip(fields, columns))
-            
+
             self.stdout.write(line.encode('utf8') + '\n')
index 6f6b9b9..2edff4e 100644 (file)
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
-from optparse import make_option
-
 from django.core.management.base import BaseCommand, CommandError
-from django.contrib.auth.models import Group, Permission
+from django.contrib.auth.models import Group
 from django.contrib.contenttypes.models import ContentType
-from django.core.exceptions import ValidationError
 
-from astakos.im.models import AstakosUser
 from ._common import add_group_permission
 
+
 class Command(BaseCommand):
     args = "<groupname> <permission> [<permissions> ...]"
     help = "Add group permissions"
-    
+
     def handle(self, *args, **options):
         if len(args) < 2:
-            raise CommandError("Please provide a group name and at least one permission")
-        
+            raise CommandError(
+                "Please provide a group name and at least one permission")
+
         group = None
         try:
             if args[0].isdigit():
@@ -57,17 +55,20 @@ class Command(BaseCommand):
                 group = Group.objects.get(name=args[0])
         except Group.DoesNotExist, e:
             raise CommandError("Invalid group")
-        
+
         try:
             content_type = ContentType.objects.get(app_label='im',
-                                                       model='astakosuser')
+                                                   model='astakosuser')
             for pname in args[1:]:
                 r, created = add_group_permission(group, pname)
                 if created:
-                    self.stdout.write('Permission: %s created successfully\n' % pname)
+                    self.stdout.write(
+                        'Permission: %s created successfully\n' % pname)
                 if r == 0:
-                    self.stdout.write('Group has already permission: %s\n' % pname)
+                    self.stdout.write(
+                        'Group has already permission: %s\n' % pname)
                 else:
-                    self.stdout.write('Permission: %s added successfully\n' % pname)
+                    self.stdout.write(
+                        'Permission: %s added successfully\n' % pname)
         except Exception, e:
-            raise CommandError(e)
\ No newline at end of file
+            raise CommandError(e)
@@ -40,14 +40,16 @@ from django.core.exceptions import ValidationError
 from astakos.im.models import AstakosUser
 from ._common import remove_group_permission
 
+
 class Command(BaseCommand):
     args = "<groupname> <permission> [<permissions> ...]"
     help = "Remove group permissions"
-    
+
     def handle(self, *args, **options):
         if len(args) < 2:
-            raise CommandError("Please provide a group name and at least one permission")
-        
+            raise CommandError(
+                "Please provide a group name and at least one permission")
+
         group = None
         try:
             if args[0].isdigit():
@@ -56,15 +58,17 @@ class Command(BaseCommand):
                 group = Group.objects.get(name=args[0])
         except Group.DoesNotExist, e:
             raise CommandError("Invalid group")
-        
+
         try:
             for pname in args[1:]:
                 r = remove_group_permission(group, pname)
                 if r < 0:
-                    self.stdout.write('Invalid permission codename: %s\n' % pname)
+                    self.stdout.write(
+                        'Invalid permission codename: %s\n' % pname)
                 elif r == 0:
                     self.stdout.write('Group has not permission: %s\n' % pname)
                 elif r > 0:
-                    self.stdout.write('Permission: %s removed successfully\n' % pname)
+                    self.stdout.write(
+                        'Permission: %s removed successfully\n' % pname)
         except Exception, e:
-            raise CommandError(e)
\ No newline at end of file
+            raise CommandError(e)
diff --git a/snf-astakos-app/astakos/im/management/commands/group-permissions-remove.py~future b/snf-astakos-app/astakos/im/management/commands/group-permissions-remove.py~future
new file mode 100644 (file)
index 0000000..581b383
--- /dev/null
@@ -0,0 +1,74 @@
+# Copyright 2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from optparse import make_option
+
+from django.core.management.base import BaseCommand, CommandError
+from django.contrib.auth.models import Group
+from django.core.exceptions import ValidationError
+
+from astakos.im.models import AstakosUser
+from ._common import remove_group_permission
+
+
+class Command(BaseCommand):
+    args = "<groupname> <permission> [<permissions> ...]"
+    help = "Remove group permissions"
+
+    def handle(self, *args, **options):
+        if len(args) < 2:
+            raise CommandError(
+                "Please provide a group name and at least one permission")
+
+        group = None
+        try:
+            if args[0].isdigit():
+                group = Group.objects.get(id=args[0])
+            else:
+                group = Group.objects.get(name=args[0])
+        except Group.DoesNotExist, e:
+            raise CommandError("Invalid group")
+
+        try:
+            for pname in args[1:]:
+                r = remove_group_permission(group, pname)
+                if r < 0:
+                    self.stdout.write(
+                        'Invalid permission codename: %s\n' % pname)
+                elif r == 0:
+                    self.stdout.write('Group has not permission: %s\n' % pname)
+                elif r > 0:
+                    self.stdout.write(
+                        'Permission: %s removed successfully\n' % pname)
+        except Exception, e:
+            raise CommandError(e)
diff --git a/snf-astakos-app/astakos/im/management/commands/group-update.py b/snf-astakos-app/astakos/im/management/commands/group-update.py
new file mode 100644 (file)
index 0000000..4ae595b
--- /dev/null
@@ -0,0 +1,110 @@
+# Copyright 2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from optparse import make_option
+
+from django.core.management.base import BaseCommand, CommandError
+
+from astakos.im.models import AstakosGroup
+from ._common import add_group_permission, remove_group_permission
+
+
+class Command(BaseCommand):
+    args = "<groupname>"
+    help = "Update group"
+
+    option_list = BaseCommand.option_list + (
+        make_option('--add-permission',
+                    dest='add-permission',
+                    help="Add user permission"),
+        make_option('--delete-permission',
+                    dest='delete-permission',
+                    help="Delete user permission"),
+        make_option('--enable',
+                    action='store_true',
+                    dest='enable',
+                    default=False,
+                    help="Enable group"),
+        make_option('--disable',
+                    action='store_true',
+                    dest='disable',
+                    default=False,
+                    help="Disable group"),
+    )
+
+    def handle(self, *args, **options):
+        if len(args) < 1:
+            raise CommandError("Please provide a group identifier")
+
+        group = None
+        try:
+            if args[0].isdigit():
+                group = AstakosGroup.objects.get(id=args[0])
+            else:
+                group = AstakosGroup.objects.get(name=args[0])
+        except AstakosGroup.DoesNotExist, e:
+            raise CommandError("Invalid group")
+
+        try:
+            pname = options.get('add-permission')
+            if pname:
+                r, created = add_group_permission(group, pname)
+                if created:
+                    self.stdout.write(
+                        'Permission: %s created successfully\n' % pname)
+                if r == 0:
+                    self.stdout.write(
+                        'Group has already permission: %s\n' % pname)
+                else:
+                    self.stdout.write(
+                        'Permission: %s added successfully\n' % pname)
+
+            pname = options.get('delete-permission')
+            if pname:
+                r = remove_group_permission(group, pname)
+                if r < 0:
+                    self.stdout.write(
+                        'Invalid permission codename: %s\n' % pname)
+                elif r == 0:
+                    self.stdout.write('Group has not permission: %s\n' % pname)
+                elif r > 0:
+                    self.stdout.write(
+                        'Permission: %s removed successfully\n' % pname)
+
+            if options.get('enable'):
+                group.enable()
+            elif options.get('disable'):
+                group.disable()
+
+        except Exception, e:
+            raise CommandError(e)
@@ -41,16 +41,16 @@ from ._common import format_bool, format_date
 class Command(BaseCommand):
     args = "<invitation ID>"
     help = "Show invitation info"
-    
+
     def handle(self, *args, **options):
         if len(args) != 1:
             raise CommandError("Please provide an invitation id")
-        
+
         try:
             invitation = Invitation.objects.get(id=int(args[0]))
         except Invitation.DoesNotExist:
             raise CommandError("Unknown invitation id '%s'" % (args[0],))
-        
+
         kv = {
             'id': invitation.id,
             'real name': invitation.realname,
@@ -62,7 +62,7 @@ class Command(BaseCommand):
             'inviter real name': invitation.inviter.realname,
             'invitater email': invitation.inviter.email,
         }
-        
+
         for key, val in sorted(kv.items()):
             line = '%s: %s\n' % (key.rjust(18), val)
             self.stdout.write(line.encode('utf8'))
index e81b413..67b5b61 100644 (file)
@@ -42,15 +42,15 @@ from ._common import format_bool
 
 class Command(BaseCommand):
     help = "List invitations"
-    
+
     option_list = BaseCommand.option_list + (
         make_option('-c',
-            action='store_true',
-            dest='csv',
-            default=False,
-            help="Use pipes to separate values"),
-        )
-    
+                    action='store_true',
+                    dest='csv',
+                    default=False,
+                    help="Use pipes to separate values"),
+    )
+
     def handle(self, *args, **options):
         if args:
             raise CommandError("Command doesn't accept any arguments")
@@ -59,23 +59,24 @@ class Command(BaseCommand):
         
         labels = ('id', 'inviter', 'email', 'real name', 'code', 'consumed')
         columns = (3, 24, 24, 24, 20, 4, 8)
-        
+
         if not options['csv']:
             line = ' '.join(l.rjust(w) for l, w in zip(labels, columns))
             self.stdout.write(line + '\n')
             sep = '-' * len(line)
             self.stdout.write(sep + '\n')
-        
+
         for invitation in invitations:
             id = str(invitation.id)
             code = str(invitation.code)
             consumed = format_bool(invitation.is_consumed)
-            fields = (id, invitation.inviter.email, invitation.username, invitation.realname,
-                      code, consumed)
-            
+            fields = (
+                id, invitation.inviter.email, invitation.username, invitation.realname,
+                code, consumed)
+
             if options['csv']:
                 line = '|'.join(fields)
             else:
                 line = ' '.join(f.rjust(w) for f, w in zip(fields, columns))
-            
+
             self.stdout.write(line.encode('utf8') + '\n')
diff --git a/snf-astakos-app/astakos/im/management/commands/quotaholder-sync.py b/snf-astakos-app/astakos/im/management/commands/quotaholder-sync.py
new file mode 100644 (file)
index 0000000..307a271
--- /dev/null
@@ -0,0 +1,52 @@
+# Copyright 2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from django.core.management.base import BaseCommand, CommandError
+
+from astakos.im.models import AstakosUser, Resource
+from astakos.im.endpoints.qh import register_users, register_resources
+
+import logging
+logger = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+    help = "Send user information and resource quota in the Quotaholder"
+
+    def handle(self, *args, **options):
+        try:
+            register_resources(Resource.objects.all())
+            register_users(AstakosUser.objects.all())
+        except BaseException, e:
+            logger.exception(e)
+            raise CommandError("Syncing failed.")
diff --git a/snf-astakos-app/astakos/im/management/commands/resource-add.py b/snf-astakos-app/astakos/im/management/commands/resource-add.py
new file mode 100644 (file)
index 0000000..d799015
--- /dev/null
@@ -0,0 +1,62 @@
+# Copyright 2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from django.core.management.base import BaseCommand, CommandError
+from django.db.utils import IntegrityError
+
+from astakos.im.models import Resource, Service
+
+
+class Command(BaseCommand):
+    args = "<service> <resource> <desc> <unit>"
+    help = "Add a resource"
+
+    def handle(self, *args, **options):
+        if len(args) < 2:
+            raise CommandError("Invalid number of arguments")
+
+        service_name = args[0]
+        resource_name = args[1]
+
+        try:
+            service = Service.objects.get(name=service_name)
+        except Service.DoesNotExist:
+            raise CommandError("Invalid service name")
+        else:
+            try:
+                resource = Resource(name=resource_name, service=service)
+                resource.save()
+            except IntegrityError, e:
+                raise CommandError(e)
+            # else:
+#                 resource.meta.add(args[2:])
diff --git a/snf-astakos-app/astakos/im/management/commands/resource-list.py b/snf-astakos-app/astakos/im/management/commands/resource-list.py
new file mode 100644 (file)
index 0000000..93bd736
--- /dev/null
@@ -0,0 +1,72 @@
+# Copyright 2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from optparse import make_option
+
+from django.core.management.base import BaseCommand
+
+from astakos.im.models import Resource
+
+
+class Command(BaseCommand):
+    help = "List resources"
+
+    option_list = BaseCommand.option_list + (
+        make_option('-c',
+                    action='store_true',
+                    dest='csv',
+                    default=False,
+                    help="Use pipes to separate values"),
+    )
+
+    def handle(self, *args, **options):
+        resources = Resource.objects.select_related().all()
+
+        labels = ('id', 'service', 'name')
+        columns = (3, 40, 40)
+
+        if not options['csv']:
+            line = ' '.join(l.rjust(w) for l, w in zip(labels, columns))
+            self.stdout.write(line + '\n')
+            sep = '-' * len(line)
+            self.stdout.write(sep + '\n')
+
+        for r in resources:
+            fields = (str(r.id), r.service.name, r.name)
+
+            if options['csv']:
+                line = '|'.join(fields)
+            else:
+                line = ' '.join(f.rjust(w) for f, w in zip(fields, columns))
+
+            self.stdout.write(line.encode('utf8') + '\n')
diff --git a/snf-astakos-app/astakos/im/management/commands/resource-remove.py b/snf-astakos-app/astakos/im/management/commands/resource-remove.py
new file mode 100644 (file)
index 0000000..da2415f
--- /dev/null
@@ -0,0 +1,57 @@
+# Copyright 2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from django.core.management.base import BaseCommand, CommandError
+
+from astakos.im.models import Resource
+
+
+class Command(BaseCommand):
+    args = "<resource>"
+    help = "Add a resource"
+
+    def handle(self, *args, **options):
+        if len(args) < 1:
+            raise CommandError("Invalid number of arguments")
+
+        kwargs = {}
+        if args[0].isdigit():
+            kwargs['id'] = args[0]
+        else:
+            kwargs['name'] = args[0]
+
+        try:
+            r = Resource.objects.get(**kwargs)
+        except Resource.DoesNotExist, e:
+            raise CommandError("Invalid resource")
+        r.delete()
index 61b864d..70cc21e 100644 (file)
 
 from django.core.management.base import BaseCommand, CommandError
 
-from astakos.im.models import Service
+from astakos.im.api.callpoint import AstakosCallpoint
 
 class Command(BaseCommand):
     args = "<name> <url> [<icon>]"
     help = "Register a service"
-    
+
     def handle(self, *args, **options):
         if len(args) < 2:
             raise CommandError("Invalid number of arguments")
-        
-        service = Service(name=args[0], url=args[1])
+
+        s = {'name':args[0], 'url':args[1]}
         if len(args) == 3:
-            service.icon = args[2]
+            s['icon'] = args[2]
         try:
-            service.save()
-            self.stdout.write('Service created with token: %s\n' % service.auth_token)
+            c = AstakosCallpoint()
+            c.add_services((s,))
         except Exception, e:
-            raise CommandError(e)
\ No newline at end of file
+            raise CommandError(e)
+        else:
+            self.stdout.write(
+                'Service created successfully\n')
index 324f312..14071c0 100644 (file)
 
 from optparse import make_option
 
-from django.core.management.base import BaseCommand, CommandError
+from django.core.management.base import NoArgsCommand
 
 from astakos.im.models import Service
 
-class Command(BaseCommand):
+
+class Command(NoArgsCommand):
     help = "List services"
 
-    option_list = BaseCommand.option_list + (
+    option_list = NoArgsCommand.option_list + (
         make_option('-c',
-            action='store_true',
-            dest='csv',
-            default=False,
-            help="Use pipes to separate values"),
+                    action='store_true',
+                    dest='csv',
+                    default=False,
+                    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):
         services = Service.objects.all().order_by('id')
 
         labels = ('id', 'name', 'url', 'auth_token', 'icon')
@@ -64,9 +62,10 @@ class Command(BaseCommand):
             self.stdout.write(sep + '\n')
 
         for service in services:
-            fields = (str(service.id), service.name, service.url,
-                    service.auth_token,
-                    service.icon)
+            fields = (str(service.id), service.name,
+                      service.url,
+                      service.auth_token or '',
+                      service.icon)
 
             if options['csv']:
                 line = '|'.join(fields)
index e071b62..9911c5f 100644 (file)
@@ -35,16 +35,17 @@ from django.core.management.base import BaseCommand, CommandError
 
 from astakos.im.models import Service
 
+
 class Command(BaseCommand):
     args = "<name>"
     help = "Unregister a service"
-    
+
     def handle(self, *args, **options):
         if len(args) < 1:
             raise CommandError("Invalid number of arguments")
-        
+
         try:
             service = Service.objects.get(name=args[0])
             service.delete()
         except Service.DoesNotExist, e:
-            raise CommandError(e)
\ No newline at end of file
+            raise CommandError(e)
index 19e889a..4c42041 100644 (file)
@@ -35,14 +35,15 @@ from django.core.management.base import BaseCommand, CommandError
 
 from astakos.im.models import Service
 
+
 class Command(BaseCommand):
     args = "<name>"
     help = "Renew service token"
-    
+
     def handle(self, *args, **options):
         if len(args) != 1:
             raise CommandError("Invalid number of arguments")
-        
+
         try:
             service = Service.objects.get(name=args[0])
             service.renew_token()
@@ -51,4 +52,4 @@ class Command(BaseCommand):
         except Service.DoesNotExist:
             raise CommandError("Invalid service name")
         except Exception, e:
-            raise CommandError(e)
\ No newline at end of file
+            raise CommandError(e)
index 8eadfb0..cdc8eec 100644 (file)
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
-from optparse import make_option
-from random import choice
-from string import digits, lowercase, uppercase
-from uuid import uuid4
-from time import time
 from os.path import abspath
 
 from django.core.management.base import BaseCommand, CommandError
 
 from astakos.im.models import ApprovalTerms
 
+
 class Command(BaseCommand):
     args = "<location>"
     help = "Insert approval terms"
-    
+
     def handle(self, *args, **options):
         if len(args) != 1:
             raise CommandError("Invalid number of arguments")
-        
+
         location = abspath(args[0].decode('utf8'))
         try:
-            f = open(location, 'r')
+            open(location, 'r')
         except IOError:
             raise CommandError("Invalid location")
-        
+
         terms = ApprovalTerms(location=location)
         terms.save()
-        
+
         msg = "Created term id %d" % (terms.id,)
         self.stdout.write(msg + '\n')
index e4d4060..e33af7e 100644 (file)
@@ -37,26 +37,28 @@ from astakos.im.functions import send_activation, SendMailError
 
 from ._common import get_user
 
+
 class Command(BaseCommand):
     args = "<user ID or email> [user ID or email] ..."
     help = "Sends an activation email to one or more users"
-    
+
     def handle(self, *args, **options):
         if not args:
             raise CommandError("No user was given")
-        
+
         for email_or_id in args:
             user = get_user(email_or_id, is_active=False)
             if not user:
                 self.stderr.write("Unknown user '%s'\n" % (email_or_id,))
                 continue
             if user.is_active:
-                self.stderr.write("Already active user '%s'\n" % (email_or_id,))
+                self.stderr.write(
+                    "Already active user '%s'\n" % (email_or_id,))
                 continue
-            
+
             try:
                 send_activation(user)
             except SendMailError, e:
                 raise CommandError(e.message)
-            
-            self.stdout.write("Activation sent to '%s'\n" % (user.email,))
\ No newline at end of file
+
+            self.stdout.write("Activation sent to '%s'\n" % (user.email,))
index 485914d..29a47f7 100644 (file)
 import socket
 
 from optparse import make_option
-from random import choice
-from string import digits, lowercase, uppercase
-from uuid import uuid4
 
 from django.core.management.base import BaseCommand, CommandError
 from django.core.validators import validate_email
 from django.core.exceptions import ValidationError
-from django.contrib.auth.models import Group
 
 from astakos.im.models import AstakosUser
-from astakos.im.util import reserved_email
+from astakos.im.api.callpoint import AstakosCallpoint
+
+def filter_custom_options(options):
+    base_dests = list(
+        getattr(o, 'dest', None) for o in BaseCommand.option_list)
+    return dict((k, v) for k, v in options.iteritems() if k not in base_dests)
 
-from ._common import add_user_permission
 
 class Command(BaseCommand):
-    args = "<email> <first name> <last name> <affiliation>"
+    args = "<email>"
     help = "Create a user"
-    
+
     option_list = BaseCommand.option_list + (
+        make_option('--first-name',
+                    dest='first_name',
+                    metavar='NAME',
+                    help="Set user's first name"),
+        make_option('--last-name',
+                    dest='last_name',
+                    metavar='NAME',
+                    help="Set user's last name"),
+        make_option('--affiliation',
+                    dest='affiliation',
+                    metavar='AFFILIATION',
+                    help="Set user's affiliation"),
+        make_option('--password',
+                    dest='password',
+                    metavar='PASSWORD',
+                    help="Set user's password"),
         make_option('--active',
-            action='store_true',
-            dest='active',
-            default=False,
-            help="Activate user"),
+                    action='store_true',
+                    dest='is_active',
+                    default=False,
+                    help="Activate user"),
         make_option('--admin',
-            action='store_true',
-            dest='admin',
-            default=False,
-            help="Give user admin rights"),
-        make_option('--password',
-            dest='password',
-            metavar='PASSWORD',
-            help="Set user's password"),
-        make_option('--add-group',
-            dest='add-group',
-            help="Add user group"),
-        make_option('--add-permission',
-            dest='add-permission',
-            help="Add user permission")
-        )
-    
+                    action='store_true',
+                    dest='is_superuser',
+                    default=False,
+                    help="Give user admin rights"),
+        make_option('-g',
+                    action='append',
+                    dest='groups',
+                    help="Add user group (may be used multiple times)"),
+        make_option('-p',
+                    action='append',
+                    dest='permissions',
+                    help="Add user permission (may be used multiple times)")
+    )
+
     def handle(self, *args, **options):
-        if len(args) != 4:
+        if len(args) != 1:
             raise CommandError("Invalid number of arguments")
-        
-        args = [a.decode('utf8') for a in args]
-        email, first, last, affiliation = args
-        
+
+        email = args[0].decode('utf8')
+
         try:
-            validate_email( email )
+            validate_email(email)
         except ValidationError:
             raise CommandError("Invalid email")
-        
-        username =  uuid4().hex[:30]
-        password = options.get('password')
-        if password is None:
-            password = AstakosUser.objects.make_random_password()
-        
-        if reserved_email(email):
-            raise CommandError("A user with this email already exists")
-        
-        user = AstakosUser(username=username, first_name=first, last_name=last,
-                           email=email, affiliation=affiliation,
-                           provider='local')
-        user.set_password(password)
-        
-        if options['active']:
-            user.is_active = True
-        if options['admin']:
-            user.is_admin = True
-        
+
+        u = {'email': email}
+        u.update(filter_custom_options(options))
+        if not u.get('password'):
+            u['password'] = AstakosUser.objects.make_random_password()
+
         try:
-            user.save()
+            c = AstakosCallpoint()
+            r = c.create_users((u,))
         except socket.error, e:
             raise CommandError(e)
         except ValidationError, e:
             raise CommandError(e)
         else:
-            msg = "Created user id %d" % (user.id,)
-            if options['password'] is None:
-                msg += " with password '%s'" % (password,)
-            self.stdout.write(msg + '\n')
-            
-            groupname = options.get('add-group')
-            if groupname is not None:
-                try:
-                    group = Group.objects.get(name=groupname)
-                    user.groups.add(group)
-                    self.stdout.write('Group: %s added successfully\n' % groupname)
-                except Group.DoesNotExist, e:
-                    self.stdout.write('Group named %s does not exist\n' % groupname)
-            
-            pname = options.get('add-permission')
-            if pname is not None:
-                try:
-                    r, created = add_user_permission(user, pname)
-                    if created:
-                        self.stdout.write('Permission: %s created successfully\n' % pname)
-                    if r > 0:
-                        self.stdout.write('Permission: %s added successfully\n' % pname)
-                    elif r==0:
-                        self.stdout.write('User has already permission: %s\n' % pname)
-                except Exception, e:
-                    raise CommandError(e)
\ No newline at end of file
+            failed = (res for res in r if not res.is_success)
+            for r in failed:
+                if not r.is_success:
+                    raise CommandError(r.reason)
+            if not failed:
+                self.stdout.write('User created successfully')
+                if not u.get('password'):
+                    self.stdout.write('with password: %s' % u['password'])
@@ -41,11 +41,11 @@ from ._common import format_bool, format_date
 class Command(BaseCommand):
     args = "<user ID or email>"
     help = "Show user info"
-    
+
     def handle(self, *args, **options):
         if len(args) != 1:
             raise CommandError("Please provide a user ID or email")
-        
+
         email_or_id = args[0]
         if email_or_id.isdigit():
             users = AstakosUser.objects.filter(id=int(email_or_id))
@@ -55,7 +55,7 @@ class Command(BaseCommand):
             field = 'id' if email_or_id.isdigit() else 'email'
             msg = "Unknown user with %s '%s'" % (field, email_or_id)
             raise CommandError(msg)
-        
+
         for user in users:
             kv = {
                 'id': user.id,
@@ -74,21 +74,23 @@ class Command(BaseCommand):
                 'provider': user.provider,
                 'verified': format_bool(user.is_verified),
                 'has_credits': format_bool(user.has_credits),
-                'groups': [elem.name for elem in user.groups.all()],
+                'groups': [elem.name for elem in user.astakos_groups.all()],
                 'permissions': [elem.codename for elem in user.user_permissions.all()],
                 'group_permissions': user.get_group_permissions(),
                 'third_party_identifier': user.third_party_identifier,
                 'email_verified': format_bool(user.email_verified),
                 'username': user.username,
-                'activation_sent_date': format_date(user.activation_sent)
+                'activation_sent_date': format_date(user.activation_sent),
+                'resources': dict(user.quota)
             }
             if get_latest_terms():
-                has_signed_terms = user.signed_terms()
+                has_signed_terms = user.signed_terms
                 kv['has_signed_terms'] = format_bool(has_signed_terms)
                 if has_signed_terms:
-                    kv['date_signed_terms'] = format_date(user.date_signed_terms)
-            
+                    kv['date_signed_terms'] = format_date(
+                        user.date_signed_terms)
+
             for key, val in sorted(kv.items()):
                 line = '%s: %s\n' % (key.rjust(22), val)
                 self.stdout.write(line.encode('utf8'))
-            self.stdout.write('\n')
\ No newline at end of file
+            self.stdout.write('\n')
index 31dfbe6..f24469b 100644 (file)
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
-import socket
-
 from django.core.management.base import BaseCommand, CommandError
 from django.db.utils import IntegrityError
 from django.db import transaction
 
-from astakos.im.functions import invite, SendMailError
-from astakos.im.models import Invitation
+from astakos.im.functions import SendMailError
 
 from ._common import get_user
 
+
 @transaction.commit_manually
 class Command(BaseCommand):
     args = "<inviter id or email> <email> <real name>"
     help = "Invite a user"
-    
+
     def handle(self, *args, **options):
         if len(args) != 3:
             raise CommandError("Invalid number of arguments")
-        
+
         inviter = get_user(args[0], is_active=True)
         if not inviter:
             raise CommandError("Unknown inviter")
-        if  not inviter.is_active:
+        if not inviter.is_active:
             raise CommandError("Inactive inviter")
-        
+
         if inviter.invitations > 0:
             email = args[1]
             realname = args[2]
-            
+
             try:
-                invitation = Invitation(username = email, realname=realname, inviter=inviter)
-                invite(invitation, inviter)
+                inviter.invite(email, realname)
                 self.stdout.write("Invitation sent to '%s'\n" % (email,))
             except SendMailError, e:
                 transaction.rollback()
                 raise CommandError(e.message)
             except IntegrityError, e:
                 transaction.rollback()
-                raise CommandError("There is already an invitation for %s" % (email,))
+                raise CommandError(
+                    "There is already an invitation for %s" % (email,))
             else:
                 transaction.commit()
         else:
index e8f912d..4e69e98 100644 (file)
 
 from optparse import make_option
 
-from django.core.management.base import BaseCommand, CommandError
+from django.core.management.base import NoArgsCommand
 
 from astakos.im.models import AstakosUser
 
 from ._common import format_bool
 
 
-class Command(BaseCommand):
+class Command(NoArgsCommand):
     help = "List users"
-    
-    option_list = BaseCommand.option_list + (
+
+    option_list = NoArgsCommand.option_list + (
         make_option('-c',
-            action='store_true',
-            dest='csv',
-            default=False,
-            help="Use pipes to separate values"),
+                    action='store_true',
+                    dest='csv',
+                    default=False,
+                    help="Use pipes to separate values"),
         make_option('-p',
-            action='store_true',
-            dest='pending',
-            default=False,
-            help="List only users pending activation"),
+                    action='store_true',
+                    dest='pending',
+                    default=False,
+                    help="List only users pending activation"),
         make_option('-n',
-            action='store_true',
-            dest='pending_send_mail',
-            default=False,
-            help="List only users who have not received activation"),
-        )
-    
-    def handle(self, *args, **options):
-        if args:
-            raise CommandError("Command doesn't accept any arguments")
-        
+                    action='store_true',
+                    dest='pending_send_mail',
+                    default=False,
+                    help="List only users who have not received activation"),
+    )
+
+    def handle_noargs(self, **options):
         users = AstakosUser.objects.all().order_by('id')
         if options['pending']:
             users = users.filter(is_active=False)
         elif options['pending_send_mail']:
             users = users.filter(is_active=False, activation_sent=None)
-        
-        labels = ('id', 'email', 'real name', 'active', 'admin', 'provider', 'groups')
+
+        labels = ('id', 'email', 'real name', 'active', 'admin', 'provider')
         columns = (3, 24, 24, 6, 5, 12, 24)
-        
+
         if not options['csv']:
             line = ' '.join(l.rjust(w) for l, w in zip(labels, columns))
             self.stdout.write(line + '\n')
             sep = '-' * len(line)
             self.stdout.write(sep + '\n')
-        
+
         for user in users:
             id = str(user.id)
             active = format_bool(user.is_active)
             admin = format_bool(user.is_superuser)
-            fields = (id, user.email, user.realname, active, admin, user.provider,
-                      ','.join([g.name for g in user.groups.all()]))
-            
+            fields = (
+                id, user.email, user.realname, active, admin, user.provider
+            )
+
             if options['csv']:
                 line = '|'.join(fields)
             else:
                 line = ' '.join(f.rjust(w) for f, w in zip(fields, columns))
-            
+
             self.stdout.write(line.encode('utf8') + '\n')
 from optparse import make_option
 
 from django.core.management.base import BaseCommand, CommandError
-from django.contrib.auth.models import Group, Permission
-from django.contrib.contenttypes.models import ContentType
+from django.contrib.auth.models import Group
 from django.core.exceptions import ValidationError
 
 from astakos.im.models import AstakosUser
 from ._common import remove_user_permission, add_user_permission
 
+
 class Command(BaseCommand):
     args = "<user ID>"
     help = "Modify a user's attributes"
-    
+
     option_list = BaseCommand.option_list + (
         make_option('--invitations',
-            dest='invitations',
-            metavar='NUM',
-            help="Update user's invitations"),
+                    dest='invitations',
+                    metavar='NUM',
+                    help="Update user's invitations"),
         make_option('--level',
-            dest='level',
-            metavar='NUM',
-            help="Update user's level"),
+                    dest='level',
+                    metavar='NUM',
+                    help="Update user's level"),
         make_option('--password',
-            dest='password',
-            metavar='PASSWORD',
-            help="Set user's password"),
+                    dest='password',
+                    metavar='PASSWORD',
+                    help="Set user's password"),
         make_option('--provider',
-            dest='provider',
-            metavar='PROVIDER',
-            help="Set user's provider"),
+                    dest='provider',
+                    metavar='PROVIDER',
+                    help="Set user's provider"),
         make_option('--renew-token',
-            action='store_true',
-            dest='renew_token',
-            default=False,
-            help="Renew the user's token"),
+                    action='store_true',
+                    dest='renew_token',
+                    default=False,
+                    help="Renew the user's token"),
         make_option('--renew-password',
-            action='store_true',
-            dest='renew_password',
-            default=False,
-            help="Renew the user's password"),
+                    action='store_true',
+                    dest='renew_password',
+                    default=False,
+                    help="Renew the user's password"),
         make_option('--set-admin',
-            action='store_true',
-            dest='admin',
-            default=False,
-            help="Give user admin rights"),
+                    action='store_true',
+                    dest='admin',
+                    default=False,
+                    help="Give user admin rights"),
         make_option('--set-noadmin',
-            action='store_true',
-            dest='noadmin',
-            default=False,
-            help="Revoke user's admin rights"),
+                    action='store_true',
+                    dest='noadmin',
+                    default=False,
+                    help="Revoke user's admin rights"),
         make_option('--set-active',
-            action='store_true',
-            dest='active',
-            default=False,
-            help="Change user's state to inactive"),
+                    action='store_true',
+                    dest='active',
+                    default=False,
+                    help="Change user's state to inactive"),
         make_option('--set-inactive',
-            action='store_true',
-            dest='inactive',
-            default=False,
-            help="Change user's state to inactive"),
+                    action='store_true',
+                    dest='inactive',
+                    default=False,
+                    help="Change user's state to inactive"),
         make_option('--add-group',
-            dest='add-group',
-            help="Add user group"),
+                    dest='add-group',
+                    help="Add user group"),
         make_option('--delete-group',
-            dest='delete-group',
-            help="Delete user group"),
+                    dest='delete-group',
+                    help="Delete user group"),
         make_option('--add-permission',
-            dest='add-permission',
-            help="Add user permission"),
+                    dest='add-permission',
+                    help="Add user permission"),
         make_option('--delete-permission',
-            dest='delete-permission',
-            help="Delete user permission"),
-        )
-    
+                    dest='delete-permission',
+                    help="Delete user permission"),
+    )
+
     def handle(self, *args, **options):
         if len(args) != 1:
             raise CommandError("Please provide a user ID")
-        
+
         if args[0].isdigit():
-            user = AstakosUser.objects.get(id=int( args[0]))
+            user = AstakosUser.objects.get(id=int(args[0]))
         else:
             raise CommandError("Invalid ID")
-        
+
         if not user:
             raise CommandError("Unknown user")
-        
+
         if options.get('admin'):
             user.is_superuser = True
         elif options.get('noadmin'):
             user.is_superuser = False
-        
+
         if options.get('active'):
             user.is_active = True
         elif options.get('inactive'):
             user.is_active = False
-        
+
         invitations = options.get('invitations')
         if invitations is not None:
             user.invitations = int(invitations)
-        
+
         groupname = options.get('add-group')
         if groupname is not None:
             try:
                 group = Group.objects.get(name=groupname)
                 user.groups.add(group)
             except Group.DoesNotExist, e:
-                self.stdout.write("Group named %s does not exist\n" % groupname)
-        
+                self.stdout.write(
+                    "Group named %s does not exist\n" % groupname)
+
         groupname = options.get('delete-group')
         if groupname is not None:
             try:
                 group = Group.objects.get(name=groupname)
                 user.groups.remove(group)
             except Group.DoesNotExist, e:
-                self.stdout.write("Group named %s does not exist\n" % groupname)
-        
+                self.stdout.write(
+                    "Group named %s does not exist\n" % groupname)
+
         pname = options.get('add-permission')
         if pname is not None:
             try:
                 r, created = add_user_permission(user, pname)
                 if created:
-                    self.stdout.write('Permission: %s created successfully\n' % pname)
+                    self.stdout.write(
+                        'Permission: %s created successfully\n' % pname)
                 if r > 0:
-                    self.stdout.write('Permission: %s added successfully\n' % pname)
-                elif r==0:
-                    self.stdout.write('User has already permission: %s\n' % pname)
+                    self.stdout.write(
+                        'Permission: %s added successfully\n' % pname)
+                elif r == 0:
+                    self.stdout.write(
+                        'User has already permission: %s\n' % pname)
             except Exception, e:
                 raise CommandError(e)
-        
-        pname  = options.get('delete-permission')
+
+        pname = options.get('delete-permission')
         if pname is not None and not user.has_perm(pname):
             try:
                 r = remove_user_permission(user, pname)
                 if r < 0:
-                    self.stdout.write('Invalid permission codename: %s\n' % pname)
+                    self.stdout.write(
+                        'Invalid permission codename: %s\n' % pname)
                 elif r == 0:
                     self.stdout.write('User has not permission: %s\n' % pname)
                 elif r > 0:
-                    self.stdout.write('Permission: %s removed successfully\n' % pname)
+                    self.stdout.write(
+                        'Permission: %s removed successfully\n' % pname)
             except Exception, e:
                 raise CommandError(e)
-        
+
         level = options.get('level')
         if level is not None:
             user.level = int(level)
-        
+
         password = options.get('password')
         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()
             user.set_password(password)
-        
+
         if options['renew_token']:
             user.renew_token()
-        
+
         try:
             user.save()
         except ValidationError, e:
             raise CommandError(e)
-        
+
         if password:
             self.stdout.write('User\'s new password: %s\n' % password)
diff --git a/snf-astakos-app/astakos/im/messages.py b/snf-astakos-app/astakos/im/messages.py
new file mode 100644 (file)
index 0000000..ac1b024
--- /dev/null
@@ -0,0 +1,128 @@
+# Copyright 2011-2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+ACCOUNT_AUTHENTICATION_FAILED           =   'Cannot authenticate account.'
+ACCOUNT_ALREADY_ACTIVE                  =   'Account is already active.'
+ACCOUNT_PENDING_ACTIVATION              =   'Your request is pending activation.'
+ACCOUNT_RESEND_ACTIVATION               =   'You have not followed the activation link. <a href="%(send_activation_url)s">Resend activation email?</a>'
+INACTIVE_ACCOUNT_CHANGE_EMAIL           =   ''.join([ACCOUNT_RESEND_ACTIVATION, ' or <a href="%(signup_url)s">Provide new email?</a>'])
+
+ACCOUNT_UNKNOWN                         =   'There is no such account.'
+TOKEN_UNKNOWN                           =   'There is no user matching this token.'
+
+PROFILE_UPDATED                         =   'Profile has been updated successfully.'
+FEEDBACK_SENT                           =   'Feedback successfully sent.'
+EMAIL_CHANGED                           =   'Account email has been changed successfully.'
+EMAIL_CHANGE_REGISTERED                 =   'Change email request has been registered succefully. \
+                                               You are going to receive a verification email in the new address.'
+
+OBJECT_CREATED                          =   'The %(verbose_name)s was created successfully.'
+MEMBER_JOINED_GROUP                     =   '%(realname)s has been successfully joined the group.'
+MEMBER_REMOVED                          =   '%(realname)s has been successfully removed from the group.'
+BILLING_ERROR                           =   'Service response status: %(status)d' 
+LOGOUT_SUCCESS                          =   'You have successfully logged out.'
+
+GENERIC_ERROR                           =   'Something wrong has happened. \
+                                               Please contact the administrators for more details.'
+
+MAX_INVITATION_NUMBER_REACHED   =           'There are no invitations left.'
+GROUP_MAX_PARTICIPANT_NUMBER_REACHED    =   'Group maximum participant number has been reached.'
+NO_APPROVAL_TERMS                       =   'There are no approval terms.'
+PENDING_EMAIL_CHANGE_REQUEST            =   'There is already a pending change email request.'
+OBJECT_CREATED_FAILED                   =   'The %(verbose_name)s creation failed: %(reason)s.'
+GROUP_JOIN_FAILURE                      =   'Failed to join group.'
+GROUPKIND_UNKNOWN                       =   'There is no such a group kind'
+NOT_MEMBER                              =   'User is not member of the group.'
+NOT_OWNER                               =   'User is not a group owner.'
+OWNER_CANNOT_LEAVE_GROUP                =   'Owner cannot leave the group.'
+
+# Field validation fields
+REQUIRED_FIELD                          =   'This field is required.'
+EMAIL_USED                              =   'This email address is already in use. Please supply a different email address.'
+SHIBBOLETH_EMAIL_USED                   =   'This email is already associated with another shibboleth account.'
+SHIBBOLETH_INACTIVE_ACC                 =   'This email is already associated with an inactive account. \
+                                               You need to wait to be activated before being able to switch to a shibboleth account.'   
+SHIBBOLETH_MISSING_EPPN                 =   'Missing unique token in request.'
+SHIBBOLETH_MISSING_NAME                 =   'Missing user name in request.'
+
+SIGN_TERMS                              =   'You have to agree with the terms.'
+CAPTCHA_VALIDATION_ERR                  =   'You have not entered the correct words.'
+SUSPENDED_LOCAL_ACC                     =   'Local login is not the current authentication method for this account.'
+UNUSABLE_PASSWORD                       =   'This account has not a usable password.'
+EMAIL_UNKNOWN                           =   'That e-mail address doesn\'t have an associated user account. \
+                                               Are you sure you\'ve registered?'
+INVITATION_EMAIL_EXISTS                 =   'There is already invitation for this email.'
+INVITATION_CONSUMED_ERR                 =   'Invitation is used.'
+UNKNOWN_USERS                           =   'Unknown users: %s'
+UNIQUE_EMAIL_IS_ACTIVE_CONSTRAIN_ERR    =   'Another account with the same email & is_active combination found.'
+INVALID_ACTIVATION_KEY                  =   'Invalid activation key.'
+NEW_EMAIL_ADDR_RESERVED                 =   'The new email address is reserved.'
+EMAIL_RESERVED                          =   'Email: %(email)s is reserved'
+NO_LOCAL_AUTH                           =   'Local login is not the current authentication method for this account.'
+SWITCH_ACCOUNT_FAILURE                  =   'Account failed to switch. Invalid parameters.'
+SWITCH_ACCOUNT_SUCCESS_WITH_PROVIDER    =   'Account failed to switch to %(provider)s.' 
+SWITCH_ACCOUNT_SUCCESS                  =   'Account successfully switched to %(provider)s.'
+
+# Field help text
+ADD_GROUP_MEMBERS_Q_HELP                =   'Add comma separated user emails, eg. user1@user.com, user2@user.com'
+ASTAKOSUSER_GROUPS_HELP                 =   'In addition to the permissions manually assigned, \
+                                               this user will also get all permissions granted to each group he/she is in.'
+EMAIL_CHANGE_NEW_ADDR_HELP              =   'Your old email address will be used until you verify your new one.'
+
+EMAIL_SEND_ERR                          =   'Failed to send %s.'
+ADMIN_NOTIFICATION_SEND_ERR             =   EMAIL_SEND_ERR % 'admin notification'
+VERIFICATION_SEND_ERR                   =   EMAIL_SEND_ERR % 'verification'
+INVITATION_SEND_ERR                     =   EMAIL_SEND_ERR % 'invitation'
+GREETING_SEND_ERR                       =   EMAIL_SEND_ERR % 'greeting'
+FEEDBACK_SEND_ERR                       =   EMAIL_SEND_ERR % 'feedback'
+CHANGE_EMAIL_SEND_ERR                   =   EMAIL_SEND_ERR % 'feedback'
+NOTIFICATION_SEND_ERR                   =   EMAIL_SEND_ERR % 'notification'
+
+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.'
+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.'
+ACTIVATION_SENT                         =   'Activation sent.'
+
+REGISTRATION_COMPLETED                  =   'Registration completed. You can now login.'
+
+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.'
index 24e5a93..081b895 100644 (file)
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
-from urllib import unquote
 from urlparse import urlunsplit, urlsplit
 
 from django.http import HttpResponse
 from django.utils.http import urlencode
 
 from astakos.im.cookie import Cookie
-from astakos.im.settings import COOKIE_NAME
 from astakos.im.util import get_query
 
+
 class CookieAuthenticationMiddleware(object):
     def process_request(self, request):
         cookie = Cookie(request)
index 1ab9845..7efbf67 100644 (file)
@@ -1,53 +1,67 @@
 # 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 'AstakosUser'
         db.create_table('im_astakosuser', (
             ('user_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.User'], unique=True, primary_key=True)),
-            ('affiliation', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
-            ('provider', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
-            ('level', self.gf('django.db.models.fields.IntegerField')(default=4)),
-            ('invitations', self.gf('django.db.models.fields.IntegerField')(default=0)),
-            ('auth_token', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)),
-            ('auth_token_created', self.gf('django.db.models.fields.DateTimeField')(null=True)),
-            ('auth_token_expires', self.gf('django.db.models.fields.DateTimeField')(null=True)),
+            ('affiliation', self.gf('django.db.models.fields.CharField')
+             (default='', max_length=255)),
+            ('provider', self.gf('django.db.models.fields.CharField')
+             (default='', max_length=255)),
+            ('level', self.gf(
+                'django.db.models.fields.IntegerField')(default=4)),
+            ('invitations', self.gf(
+                'django.db.models.fields.IntegerField')(default=0)),
+            ('auth_token', self.gf('django.db.models.fields.CharField')
+             (max_length=32, null=True, blank=True)),
+            ('auth_token_created', self.gf(
+                'django.db.models.fields.DateTimeField')(null=True)),
+            ('auth_token_expires', self.gf(
+                'django.db.models.fields.DateTimeField')(null=True)),
             ('updated', self.gf('django.db.models.fields.DateTimeField')()),
-            ('is_verified', self.gf('django.db.models.fields.BooleanField')(default=False)),
+            ('is_verified', self.gf(
+                'django.db.models.fields.BooleanField')(default=False)),
         ))
         db.send_create_signal('im', ['AstakosUser'])
 
         # Adding model 'Invitation'
         db.create_table('im_invitation', (
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('id', self.gf(
+                'django.db.models.fields.AutoField')(primary_key=True)),
             ('inviter', self.gf('django.db.models.fields.related.ForeignKey')(related_name='invitations_sent', null=True, to=orm['im.AstakosUser'])),
-            ('realname', self.gf('django.db.models.fields.CharField')(max_length=255)),
-            ('username', self.gf('django.db.models.fields.CharField')(max_length=255)),
-            ('code', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
-            ('is_accepted', self.gf('django.db.models.fields.BooleanField')(default=False)),
-            ('is_consumed', self.gf('django.db.models.fields.BooleanField')(default=False)),
-            ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
-            ('accepted', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
-            ('consumed', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
+            ('realname', self.gf(
+                'django.db.models.fields.CharField')(max_length=255)),
+            ('username', self.gf(
+                'django.db.models.fields.CharField')(max_length=255)),
+            ('code', self.gf(
+                'django.db.models.fields.BigIntegerField')(db_index=True)),
+            ('is_accepted', self.gf(
+                'django.db.models.fields.BooleanField')(default=False)),
+            ('is_consumed', self.gf(
+                'django.db.models.fields.BooleanField')(default=False)),
+            ('created', self.gf('django.db.models.fields.DateTimeField')
+             (auto_now_add=True, blank=True)),
+            ('accepted', self.gf('django.db.models.fields.DateTimeField')
+             (null=True, blank=True)),
+            ('consumed', self.gf('django.db.models.fields.DateTimeField')
+             (null=True, blank=True)),
         ))
         db.send_create_signal('im', ['Invitation'])
 
-
     def backwards(self, orm):
-        
+
         # Deleting model 'AstakosUser'
         db.delete_table('im_astakosuser')
 
         # Deleting model 'Invitation'
         db.delete_table('im_invitation')
 
-
     models = {
         'auth.group': {
             'Meta': {'object_name': 'Group'},
index 44e9041..6b57ea5 100644 (file)
@@ -1,23 +1,20 @@
 # encoding: utf-8
-import datetime
 from south.db import db
 from south.v2 import SchemaMigration
-from django.db import models
+
 
 class Migration(SchemaMigration):
 
     def forwards(self, orm):
-        
+
         # Adding field 'AstakosUser.third_party_identifier'
         db.add_column('im_astakosuser', 'third_party_identifier', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True), keep_default=False)
 
-
     def backwards(self, orm):
-        
+
         # Deleting field 'AstakosUser.third_party_identifier'
         db.delete_column('im_astakosuser', 'third_party_identifier')
 
-
     models = {
         'auth.group': {
             'Meta': {'object_name': 'Group'},
index 3c67560..63b2011 100644 (file)
@@ -1,23 +1,20 @@
 # 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 unique constraint on 'Invitation', fields ['username']
         db.create_unique('im_invitation', ['username'])
 
-
     def backwards(self, orm):
-        
+
         # Removing unique constraint on 'Invitation', fields ['username']
         db.delete_unique('im_invitation', ['username'])
 
-
     models = {
         'auth.group': {
             'Meta': {'object_name': 'Group'},
index 6b27b42..14927d6 100644 (file)
@@ -1,23 +1,20 @@
 # encoding: utf-8
-import datetime
 from south.db import db
 from south.v2 import SchemaMigration
-from django.db import models
+
 
 class Migration(SchemaMigration):
 
     def forwards(self, orm):
-        
+
         # Adding field 'AstakosUser.email_verified'
         db.add_column('im_astakosuser', 'email_verified', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
 
-
     def backwards(self, orm):
-        
+
         # Deleting field 'AstakosUser.email_verified'
         db.delete_column('im_astakosuser', 'email_verified')
 
-
     models = {
         'auth.group': {
             'Meta': {'object_name': 'Group'},
index 8f15e7d..74ef2ed 100644 (file)
@@ -1,23 +1,20 @@
 # encoding: utf-8
-import datetime
 from south.db import db
 from south.v2 import SchemaMigration
-from django.db import models
+
 
 class Migration(SchemaMigration):
 
     def forwards(self, orm):
-        
+
         # Adding field 'AstakosUser.has_credits'
         db.add_column('im_astakosuser', 'has_credits', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
 
-
     def backwards(self, orm):
-        
+
         # Deleting field 'AstakosUser.has_credits'
         db.delete_column('im_astakosuser', 'has_credits')
 
-
     models = {
         'auth.group': {
             'Meta': {'object_name': 'Group'},
index f10e4a2..9d2f805 100644 (file)
@@ -2,17 +2,19 @@
 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 'ApprovalTerms'
         db.create_table('im_approvalterms', (
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('id', self.gf(
+                'django.db.models.fields.AutoField')(primary_key=True)),
             ('date', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(2012, 3, 20, 14, 24, 30, 616341), db_index=True)),
-            ('location', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('location', self.gf(
+                'django.db.models.fields.CharField')(max_length=255)),
         ))
         db.send_create_signal('im', ['ApprovalTerms'])
 
@@ -22,9 +24,8 @@ class Migration(SchemaMigration):
         # Adding field 'AstakosUser.date_signed_terms'
         db.add_column('im_astakosuser', 'date_signed_terms', self.gf('django.db.models.fields.DateTimeField')(null=True), keep_default=False)
 
-
     def backwards(self, orm):
-        
+
         # Deleting model 'ApprovalTerms'
         db.delete_table('im_approvalterms')
 
@@ -34,7 +35,6 @@ class Migration(SchemaMigration):
         # Deleting field 'AstakosUser.date_signed_terms'
         db.delete_column('im_astakosuser', 'date_signed_terms')
 
-
     models = {
         'auth.group': {
             'Meta': {'object_name': 'Group'},
index 5aa8322..a9d19a0 100644 (file)
@@ -1,59 +1,70 @@
 # encoding: utf-8
-import datetime
 from south.db import db
 from south.v2 import SchemaMigration
-from django.db import models
+
 
 class Migration(SchemaMigration):
-    
+
     def forwards(self, orm):
-        
+
         # Changing field 'AstakosUser.email_verified'
-        db.alter_column('im_astakosuser', 'email_verified', self.gf('django.db.models.fields.BooleanField')(blank=True))
+        db.alter_column('im_astakosuser', 'email_verified', self.gf(
+            'django.db.models.fields.BooleanField')(blank=True))
 
         # Changing field 'AstakosUser.has_credits'
-        db.alter_column('im_astakosuser', 'has_credits', self.gf('django.db.models.fields.BooleanField')(blank=True))
+        db.alter_column('im_astakosuser', 'has_credits', self.gf(
+            'django.db.models.fields.BooleanField')(blank=True))
 
         # Changing field 'AstakosUser.date_signed_terms'
-        db.alter_column('im_astakosuser', 'date_signed_terms', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True))
+        db.alter_column('im_astakosuser', 'date_signed_terms', self.gf(
+            'django.db.models.fields.DateTimeField')(null=True, blank=True))
 
         # Changing field 'AstakosUser.is_verified'
-        db.alter_column('im_astakosuser', 'is_verified', self.gf('django.db.models.fields.BooleanField')(blank=True))
+        db.alter_column('im_astakosuser', 'is_verified', self.gf(
+            'django.db.models.fields.BooleanField')(blank=True))
 
         # Changing field 'AstakosUser.has_signed_terms'
-        db.alter_column('im_astakosuser', 'has_signed_terms', self.gf('django.db.models.fields.BooleanField')(blank=True))
+        db.alter_column('im_astakosuser', 'has_signed_terms', self.gf(
+            'django.db.models.fields.BooleanField')(blank=True))
 
         # Changing field 'Invitation.is_accepted'
-        db.alter_column('im_invitation', 'is_accepted', self.gf('django.db.models.fields.BooleanField')(blank=True))
+        db.alter_column('im_invitation', 'is_accepted', self.gf(
+            'django.db.models.fields.BooleanField')(blank=True))
 
         # Changing field 'Invitation.is_consumed'
-        db.alter_column('im_invitation', 'is_consumed', self.gf('django.db.models.fields.BooleanField')(blank=True))
-    
-    
+        db.alter_column('im_invitation', 'is_consumed', self.gf(
+            'django.db.models.fields.BooleanField')(blank=True))
+
     def backwards(self, orm):
-        
+
         # Changing field 'AstakosUser.email_verified'
-        db.alter_column('im_astakosuser', 'email_verified', self.gf('django.db.models.fields.BooleanField')())
+        db.alter_column('im_astakosuser', 'email_verified',
+                        self.gf('django.db.models.fields.BooleanField')())
 
         # Changing field 'AstakosUser.has_credits'
-        db.alter_column('im_astakosuser', 'has_credits', self.gf('django.db.models.fields.BooleanField')())
+        db.alter_column('im_astakosuser', 'has_credits', self.gf(
+            'django.db.models.fields.BooleanField')())
 
         # Changing field 'AstakosUser.date_signed_terms'
-        db.alter_column('im_astakosuser', 'date_signed_terms', self.gf('django.db.models.fields.DateTimeField')(null=True))
+        db.alter_column('im_astakosuser', 'date_signed_terms', self.gf(
+            'django.db.models.fields.DateTimeField')(null=True))
 
         # Changing field 'AstakosUser.is_verified'
-        db.alter_column('im_astakosuser', 'is_verified', self.gf('django.db.models.fields.BooleanField')())
+        db.alter_column('im_astakosuser', 'is_verified', self.gf(
+            'django.db.models.fields.BooleanField')())
 
         # Changing field 'AstakosUser.has_signed_terms'
-        db.alter_column('im_astakosuser', 'has_signed_terms', self.gf('django.db.models.fields.BooleanField')())
+        db.alter_column('im_astakosuser', 'has_signed_terms',
+                        self.gf('django.db.models.fields.BooleanField')())
 
         # Changing field 'Invitation.is_accepted'
-        db.alter_column('im_invitation', 'is_accepted', self.gf('django.db.models.fields.BooleanField')())
+        db.alter_column('im_invitation', 'is_accepted', self.gf(
+            'django.db.models.fields.BooleanField')())
 
         # Changing field 'Invitation.is_consumed'
-        db.alter_column('im_invitation', 'is_consumed', self.gf('django.db.models.fields.BooleanField')())
-    
-    
+        db.alter_column('im_invitation', 'is_consumed', self.gf(
+            'django.db.models.fields.BooleanField')())
+
     models = {
         'auth.group': {
             'Meta': {'object_name': 'Group'},
@@ -129,5 +140,5 @@ class Migration(SchemaMigration):
             'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
         }
     }
-    
+
     complete_apps = ['im']
index 632938b..e183293 100644 (file)
@@ -4,26 +4,25 @@ from south.db import db
 from south.v2 import SchemaMigration
 from django.db import models
 
+
 class Migration(SchemaMigration):
 
     def forwards(self, orm):
-        
+
         # Deleting field 'Invitation.accepted'
         db.delete_column('im_invitation', 'accepted')
 
         # Deleting field 'Invitation.is_accepted'
         db.delete_column('im_invitation', 'is_accepted')
 
-
     def backwards(self, orm):
-        
+
         # Adding field 'Invitation.accepted'
         db.add_column('im_invitation', 'accepted', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True), keep_default=False)
 
         # Adding field 'Invitation.is_accepted'
         db.add_column('im_invitation', 'is_accepted', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
 
-
     models = {
         'auth.group': {
             'Meta': {'object_name': 'Group'},
index 7b460d6..ecf0fcd 100644 (file)
@@ -2,29 +2,31 @@
 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 'EmailChange'
         db.create_table('im_emailchange', (
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
-            ('new_email_address', self.gf('django.db.models.fields.EmailField')(max_length=75)),
+            ('id', self.gf(
+                'django.db.models.fields.AutoField')(primary_key=True)),
+            ('new_email_address', self.gf(
+                'django.db.models.fields.EmailField')(max_length=75)),
             ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='emailchange_user', unique=True, to=orm['im.AstakosUser'])),
-            ('requested_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(2012, 5, 3, 12, 23, 46, 711119))),
-            ('activation_key', self.gf('django.db.models.fields.CharField')(unique=True, max_length=40, db_index=True)),
+            ('requested_at', self.gf('django.db.models.fields.DateTimeField')
+             (default=datetime.datetime(2012, 5, 3, 12, 23, 46, 711119))),
+            ('activation_key', self.gf('django.db.models.fields.CharField')
+             (unique=True, max_length=40, db_index=True)),
         ))
         db.send_create_signal('im', ['EmailChange'])
 
-
     def backwards(self, orm):
-        
+
         # Deleting model 'EmailChange'
         db.delete_table('im_emailchange')
 
-
     models = {
         'auth.group': {
             'Meta': {'object_name': 'Group'},
index 3c5931a..ed58ec5 100644 (file)
@@ -1,32 +1,36 @@
 # 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 'Service'
         db.create_table('im_service', (
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
-            ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)),
-            ('url', self.gf('django.db.models.fields.URLField')(max_length=200)),
-            ('icon', self.gf('django.db.models.fields.FilePathField')(max_length=100)),
-            ('auth_token', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)),
-            ('auth_token_created', self.gf('django.db.models.fields.DateTimeField')(null=True)),
-            ('auth_token_expires', self.gf('django.db.models.fields.DateTimeField')(null=True)),
+            ('id', self.gf(
+                'django.db.models.fields.AutoField')(primary_key=True)),
+            ('name', self.gf('django.db.models.fields.CharField')
+             (unique=True, max_length=255)),
+            ('url', self.gf('django.db.models.fields.URLField')
+             (max_length=200)),
+            ('icon', self.gf(
+                'django.db.models.fields.FilePathField')(max_length=100)),
+            ('auth_token', self.gf('django.db.models.fields.CharField')
+             (max_length=32, null=True, blank=True)),
+            ('auth_token_created', self.gf(
+                'django.db.models.fields.DateTimeField')(null=True)),
+            ('auth_token_expires', self.gf(
+                'django.db.models.fields.DateTimeField')(null=True)),
         ))
         db.send_create_signal('im', ['Service'])
 
-
     def backwards(self, orm):
-        
+
         # Deleting model 'Service'
         db.delete_table('im_service')
 
-
     models = {
         'auth.group': {
             'Meta': {'object_name': 'Group'},
index c9bd539..ded8a49 100644 (file)
@@ -1,28 +1,27 @@
 # encoding: utf-8
-import datetime
 from south.db import db
 from south.v2 import SchemaMigration
-from django.db import models
+
 
 class Migration(SchemaMigration):
 
     def forwards(self, orm):
-        
+
         # Adding field 'AstakosUser.activation_sent'
         db.add_column('im_astakosuser', 'activation_sent', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True), keep_default=False)
 
         # Changing field 'Service.url'
-        db.alter_column('im_service', 'url', self.gf('django.db.models.fields.FilePathField')(max_length=100))
-
+        db.alter_column('im_service', 'url', self.gf(
+            'django.db.models.fields.FilePathField')(max_length=100))
 
     def backwards(self, orm):
-        
+
         # Deleting field 'AstakosUser.activation_sent'
         db.delete_column('im_astakosuser', 'activation_sent')
 
         # Changing field 'Service.url'
-        db.alter_column('im_service', 'url', self.gf('django.db.models.fields.URLField')(max_length=200))
-
+        db.alter_column('im_service', 'url', self.gf(
+            'django.db.models.fields.URLField')(max_length=200))
 
     models = {
         'auth.group': {
index 4cf061c..fa142d4 100644 (file)
@@ -1,8 +1,7 @@
 # encoding: utf-8
 import datetime
-from south.db import db
 from south.v2 import DataMigration
-from django.db import models
+
 
 class Migration(DataMigration):
 
index a6c5738..f0f08b7 100644 (file)
@@ -1,28 +1,28 @@
 # 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 'AdditionalMail'
         db.create_table('im_additionalmail', (
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
-            ('owner', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['im.AstakosUser'])),
-            ('email', self.gf('django.db.models.fields.EmailField')(unique=True, max_length=75)),
+            ('id', self.gf(
+                'django.db.models.fields.AutoField')(primary_key=True)),
+            ('owner', self.gf('django.db.models.fields.related.ForeignKey')
+             (to=orm['im.AstakosUser'])),
+            ('email', self.gf('django.db.models.fields.EmailField')
+             (unique=True, max_length=75)),
         ))
         db.send_create_signal('im', ['AdditionalMail'])
 
-
     def backwards(self, orm):
-        
+
         # Deleting model 'AdditionalMail'
         db.delete_table('im_additionalmail')
 
-
     models = {
         'auth.group': {
             'Meta': {'object_name': 'Group'},
index 99c3eca..4d43cbe 100644 (file)
@@ -1,23 +1,20 @@
 # 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):
-        
+
         # Removing unique constraint on 'AdditionalMail', fields ['email']
         db.delete_unique('im_additionalmail', ['email'])
 
-
     def backwards(self, orm):
-        
+
         # Adding unique constraint on 'AdditionalMail', fields ['email']
         db.create_unique('im_additionalmail', ['email'])
 
-
     models = {
         'auth.group': {
             'Meta': {'object_name': 'Group'},
index 820a7cc..349163c 100644 (file)
@@ -1,22 +1,21 @@
 # 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 unique constraint on 'AstakosUser', fields ['third_party_identifier', 'provider']
-        db.create_unique('im_astakosuser', ['third_party_identifier', 'provider'])
 
+        # Adding unique constraint on 'AstakosUser', fields ['third_party_identifier', 'provider']
+        db.create_unique(
+            'im_astakosuser', ['third_party_identifier', 'provider'])
 
     def backwards(self, orm):
-        
-        # Removing unique constraint on 'AstakosUser', fields ['third_party_identifier', 'provider']
-        db.delete_unique('im_astakosuser', ['third_party_identifier', 'provider'])
 
+        # Removing unique constraint on 'AstakosUser', fields ['third_party_identifier', 'provider']
+        db.delete_unique(
+            'im_astakosuser', ['third_party_identifier', 'provider'])
 
     models = {
         'auth.group': {
diff --git a/snf-astakos-app/astakos/im/migrations/0015_auto__add_groupkind__add_astakosgroup__add_resourcemetadata__add_astak.py b/snf-astakos-app/astakos/im/migrations/0015_auto__add_groupkind__add_astakosgroup__add_resourcemetadata__add_astak.py
new file mode 100644 (file)
index 0000000..53bb523
--- /dev/null
@@ -0,0 +1,336 @@
+# 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 'GroupKind'
+        db.create_table('im_groupkind', (
+            ('id', self.gf(
+                'django.db.models.fields.AutoField')(primary_key=True)),
+            ('name', self.gf('django.db.models.fields.CharField')
+             (unique=True, max_length=255, db_index=True)),
+        ))
+        db.send_create_signal('im', ['GroupKind'])
+
+        # Adding model 'AstakosGroup'
+        db.create_table('im_astakosgroup', (
+            ('group_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.Group'], unique=True, primary_key=True)),
+            ('kind', self.gf('django.db.models.fields.related.ForeignKey')
+             (to=orm['im.GroupKind'])),
+            ('desc', self.gf('django.db.models.fields.TextField')(null=True)),
+            ('creation_date', self.gf('django.db.models.fields.DateTimeField')
+             (default=datetime.datetime(2012, 8, 3, 11, 26, 47, 642626))),
+            ('issue_date', self.gf(
+                'django.db.models.fields.DateTimeField')(null=True)),
+            ('expiration_date', self.gf(
+                'django.db.models.fields.DateTimeField')(null=True)),
+            ('moderation_enabled', self.gf(
+                'django.db.models.fields.BooleanField')(default=False)),
+            ('approval_date', self.gf('django.db.models.fields.DateTimeField')
+             (null=True, blank=True)),
+            ('estimated_participants', self.gf(
+                'django.db.models.fields.PositiveIntegerField')(null=True)),
+        ))
+        db.send_create_signal('im', ['AstakosGroup'])
+
+        # Adding model 'ResourceMetadata'
+        db.create_table('im_resourcemetadata', (
+            ('id', self.gf(
+                'django.db.models.fields.AutoField')(primary_key=True)),
+            ('key', self.gf('django.db.models.fields.CharField')
+             (unique=True, max_length=255, db_index=True)),
+            ('value', self.gf(
+                'django.db.models.fields.CharField')(max_length=255)),
+        ))
+        db.send_create_signal('im', ['ResourceMetadata'])
+
+        # Adding model 'AstakosGroupQuota'
+        db.create_table('im_astakosgroupquota', (
+            ('id', self.gf(
+                'django.db.models.fields.AutoField')(primary_key=True)),
+            ('limit', self.gf(
+                'django.db.models.fields.PositiveIntegerField')()),
+            ('resource', self.gf('django.db.models.fields.related.ForeignKey')
+             (to=orm['im.Resource'])),
+            ('group', self.gf('django.db.models.fields.related.ForeignKey')
+             (to=orm['im.AstakosGroup'], blank=True)),
+        ))
+        db.send_create_signal('im', ['AstakosGroupQuota'])
+
+        # Adding unique constraint on 'AstakosGroupQuota', fields ['resource', 'group']
+        db.create_unique('im_astakosgroupquota', ['resource_id', 'group_id'])
+
+        # Adding model 'Resource'
+        db.create_table('im_resource', (
+            ('id', self.gf(
+                'django.db.models.fields.AutoField')(primary_key=True)),
+            ('name', self.gf('django.db.models.fields.CharField')
+             (unique=True, max_length=255, db_index=True)),
+            ('service', self.gf('django.db.models.fields.related.ForeignKey')
+             (to=orm['im.Service'])),
+        ))
+        db.send_create_signal('im', ['Resource'])
+
+        # Adding M2M table for field meta on 'Resource'
+        db.create_table('im_resource_meta', (
+            ('id', models.AutoField(
+                verbose_name='ID', primary_key=True, auto_created=True)),
+            ('resource', models.ForeignKey(orm['im.resource'], null=False)),
+            ('resourcemetadata', models.ForeignKey(orm[
+             'im.resourcemetadata'], null=False))
+        ))
+        db.create_unique(
+            'im_resource_meta', ['resource_id', 'resourcemetadata_id'])
+
+        # Adding model 'Membership'
+        db.create_table('im_membership', (
+            ('id', self.gf(
+                'django.db.models.fields.AutoField')(primary_key=True)),
+            ('person', self.gf('django.db.models.fields.related.ForeignKey')
+             (to=orm['im.AstakosUser'])),
+            ('group', self.gf('django.db.models.fields.related.ForeignKey')
+             (to=orm['im.AstakosGroup'])),
+            ('date_requested', self.gf('django.db.models.fields.DateField')
+             (default=datetime.datetime(2012, 8, 3, 11, 26, 47, 646518))),
+            ('date_joined', self.gf('django.db.models.fields.DateField')
+             (null=True, db_index=True)),
+        ))
+        db.send_create_signal('im', ['Membership'])
+
+        # Adding unique constraint on 'Membership', fields ['person', 'group']
+        db.create_unique('im_membership', ['person_id', 'group_id'])
+
+        # Adding model 'AstakosUserQuota'
+        db.create_table('im_astakosuserquota', (
+            ('id', self.gf(
+                'django.db.models.fields.AutoField')(primary_key=True)),
+            ('limit', self.gf(
+                'django.db.models.fields.PositiveIntegerField')()),
+            ('resource', self.gf('django.db.models.fields.related.ForeignKey')
+             (to=orm['im.Resource'])),
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')
+             (to=orm['im.AstakosUser'])),
+        ))
+        db.send_create_signal('im', ['AstakosUserQuota'])
+
+        # Adding unique constraint on 'AstakosUserQuota', fields ['resource', 'user']
+        db.create_unique('im_astakosuserquota', ['resource_id', 'user_id'])
+
+        # Adding index on 'Service', fields ['name']
+        db.create_index('im_service', ['name'])
+
+        # Adding M2M table for field owner on 'AstakosUser'
+        db.create_table('im_astakosuser_owner', (
+            ('id', models.AutoField(
+                verbose_name='ID', primary_key=True, auto_created=True)),
+            ('astakosuser', models.ForeignKey(orm[
+             'im.astakosuser'], null=False)),
+            ('astakosgroup', models.ForeignKey(orm[
+             'im.astakosgroup'], null=False))
+        ))
+        db.create_unique(
+            'im_astakosuser_owner', ['astakosuser_id', 'astakosgroup_id'])
+
+    def backwards(self, orm):
+
+        # Removing index on 'Service', fields ['name']
+        db.delete_index('im_service', ['name'])
+
+        # Removing unique constraint on 'AstakosUserQuota', fields ['resource', 'user']
+        db.delete_unique('im_astakosuserquota', ['resource_id', 'user_id'])
+
+        # Removing unique constraint on 'Membership', fields ['person', 'group']
+        db.delete_unique('im_membership', ['person_id', 'group_id'])
+
+        # Removing unique constraint on 'AstakosGroupQuota', fields ['resource', 'group']
+        db.delete_unique('im_astakosgroupquota', ['resource_id', 'group_id'])
+
+        # Deleting model 'GroupKind'
+        db.delete_table('im_groupkind')
+
+        # Deleting model 'AstakosGroup'
+        db.delete_table('im_astakosgroup')
+
+        # Deleting model 'ResourceMetadata'
+        db.delete_table('im_resourcemetadata')
+
+        # Deleting model 'AstakosGroupQuota'
+        db.delete_table('im_astakosgroupquota')
+
+        # Deleting model 'Resource'
+        db.delete_table('im_resource')
+
+        # Removing M2M table for field meta on 'Resource'
+        db.delete_table('im_resource_meta')
+
+        # Deleting model 'Membership'
+        db.delete_table('im_membership')
+
+        # Deleting model 'AstakosUserQuota'
+        db.delete_table('im_astakosuserquota')
+
+        # Removing M2M table for field owner on 'AstakosUser'
+        db.delete_table('im_astakosuser_owner')
+
+    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, 8, 3, 11, 26, 47, 648667)', '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, 8, 3, 11, 26, 47, 642626)'}),
+            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'estimated_participants': ('django.db.models.fields.PositiveIntegerField', [], {'null': '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'}),
+            'issue_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'kind': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.GroupKind']"}),
+            'moderation_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            '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', [], {}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"})
+        },
+        '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', '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'}),
+            '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', '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.astakosuserquota': {
+            'Meta': {'unique_together': "(('resource', 'user'),)", 'object_name': 'AstakosUserQuota'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'limit': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}),
+            '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, 8, 3, 11, 26, 47, 650373)'}),
+            '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.membership': {
+            'Meta': {'unique_together': "(('person', 'group'),)", 'object_name': 'Membership'},
+            'date_joined': ('django.db.models.fields.DateField', [], {'null': 'True', 'db_index': 'True'}),
+            'date_requested': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime(2012, 8, 3, 11, 26, 47, 646518)'}),
+            '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.resource': {
+            'Meta': {'object_name': 'Resource'},
+            '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']"})
+        },
+        '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'})
+        }
+    }
+
+    complete_apps = ['im']
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
new file mode 100644 (file)
index 0000000..987e5b4
--- /dev/null
@@ -0,0 +1,171 @@
+# encoding: utf-8
+
+from south.v2 import DataMigration
+
+
+class Migration(DataMigration):
+
+    def forwards(self, orm):
+        "Obsolete migration."
+        return
+
+    def backwards(self, orm):
+        "Obsolete migration."
+        return
+
+    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, 8, 9, 11, 14, 9, 289091)', '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, 8, 9, 11, 14, 9, 283154)'}),
+            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'estimated_participants': ('django.db.models.fields.PositiveIntegerField', [], {'null': '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'}),
+            'issue_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'kind': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.GroupKind']"}),
+            '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', [], {}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"})
+        },
+        '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', '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'}),
+            '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', '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.astakosuserquota': {
+            'Meta': {'unique_together': "(('resource', 'user'),)", 'object_name': 'AstakosUserQuota'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'limit': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}),
+            '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, 8, 9, 11, 14, 9, 290713)'}),
+            '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.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, 8, 9, 11, 14, 9, 286925)', '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.resource': {
+            'Meta': {'object_name': 'Resource'},
+            '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']"})
+        },
+        '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'})
+        }
+    }
+
+    complete_apps = ['im']
diff --git a/snf-astakos-app/astakos/im/migrations/0018_auto__add_field_astakosgroup_homepage.py b/snf-astakos-app/astakos/im/migrations/0018_auto__add_field_astakosgroup_homepage.py
new file mode 100644 (file)
index 0000000..3632df1
--- /dev/null
@@ -0,0 +1,176 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+
+        # Adding field 'AstakosGroup.homepage'
+        db.add_column('im_astakosgroup', 'homepage', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True), keep_default=False)
+
+    def backwards(self, orm):
+
+        # Deleting field 'AstakosGroup.homepage'
+        db.delete_column('im_astakosgroup', 'homepage')
+
+    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, 9, 6, 16, 11, 52, 429294)', '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, 9, 6, 16, 11, 52, 423356)'}),
+            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'estimated_participants': ('django.db.models.fields.PositiveIntegerField', [], {'null': '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.CharField', [], {'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']"}),
+            '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', [], {}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"})
+        },
+        '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', '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'}),
+            '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', '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.astakosuserquota': {
+            'Meta': {'unique_together': "(('resource', 'user'),)", 'object_name': 'AstakosUserQuota'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'limit': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}),
+            '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, 9, 6, 16, 11, 52, 430810)'}),
+            '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.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, 9, 6, 16, 11, 52, 427324)', '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.resource': {
+            'Meta': {'object_name': 'Resource'},
+            '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']"})
+        },
+        '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'})
+        }
+    }
+
+    complete_apps = ['im']
diff --git a/snf-astakos-app/astakos/im/migrations/0020_auto__chg_field_astakosgroup_homepage.py b/snf-astakos-app/astakos/im/migrations/0020_auto__chg_field_astakosgroup_homepage.py
new file mode 100644 (file)
index 0000000..feffd45
--- /dev/null
@@ -0,0 +1,178 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+
+        # Changing field 'AstakosGroup.homepage'
+        db.alter_column('im_astakosgroup', 'homepage', self.gf(
+            'django.db.models.fields.URLField')(max_length=255, null=True))
+
+    def backwards(self, orm):
+
+        # Changing field 'AstakosGroup.homepage'
+        db.alter_column('im_astakosgroup', 'homepage', self.gf(
+            'django.db.models.fields.CharField')(max_length=255, null=True))
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        '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, 9, 18, 14, 54, 13, 70454)', '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, 9, 18, 14, 54, 13, 64622)'}),
+            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'estimated_participants': ('django.db.models.fields.PositiveIntegerField', [], {'null': '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']"}),
+            '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', [], {}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"})
+        },
+        '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', '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'}),
+            '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', '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.astakosuserquota': {
+            'Meta': {'unique_together': "(('resource', 'user'),)", 'object_name': 'AstakosUserQuota'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'limit': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}),
+            '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, 9, 18, 14, 54, 13, 72050)'}),
+            '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.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, 9, 18, 14, 54, 13, 68464)', '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.resource': {
+            'Meta': {'object_name': 'Resource'},
+            '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']"})
+        },
+        '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'})
+        }
+    }
+
+    complete_apps = ['im']
diff --git a/snf-astakos-app/astakos/im/migrations/0021_auto__add_field_astakosgroupquota_uplimit__add_field_astakosuserquota_.py b/snf-astakos-app/astakos/im/migrations/0021_auto__add_field_astakosgroupquota_uplimit__add_field_astakosuserquota_.py
new file mode 100644 (file)
index 0000000..688cfa6
--- /dev/null
@@ -0,0 +1,184 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+
+        # Adding field 'AstakosGroupQuota.uplimit'
+        db.add_column('im_astakosgroupquota', 'uplimit', self.gf('django.db.models.fields.BigIntegerField')(null=True), keep_default=False)
+
+        # Adding field 'AstakosUserQuota.uplimit'
+        db.add_column('im_astakosuserquota', 'uplimit', self.gf('django.db.models.fields.BigIntegerField')(null=True), keep_default=False)
+
+    def backwards(self, orm):
+
+        # Deleting field 'AstakosGroupQuota.uplimit'
+        db.delete_column('im_astakosgroupquota', 'uplimit')
+
+        # Deleting field 'AstakosUserQuota.uplimit'
+        db.delete_column('im_astakosuserquota', 'uplimit')
+
+    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, 9, 18, 14, 54, 24, 940454)', '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, 9, 18, 14, 54, 24, 934376)'}),
+            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'estimated_participants': ('django.db.models.fields.PositiveIntegerField', [], {'null': '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']"}),
+            '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', [], {}),
+            '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', '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'}),
+            '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', '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.astakosuserquota': {
+            'Meta': {'unique_together': "(('resource', 'user'),)", 'object_name': 'AstakosUserQuota'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'limit': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            '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, 9, 18, 14, 54, 24, 942004)'}),
+            '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.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, 9, 18, 14, 54, 24, 938314)', '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.resource': {
+            'Meta': {'object_name': 'Resource'},
+            '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']"})
+        },
+        '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'})
+        }
+    }
+
+    complete_apps = ['im']
diff --git a/snf-astakos-app/astakos/im/migrations/0022_copy_limit_to_uplimit.py b/snf-astakos-app/astakos/im/migrations/0022_copy_limit_to_uplimit.py
new file mode 100644 (file)
index 0000000..eefa310
--- /dev/null
@@ -0,0 +1,176 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+
+class Migration(DataMigration):
+
+    def forwards(self, orm):
+        for q in orm.AstakosGroupQuota.objects.all():
+            q.uplimit = q.limit
+            q.save()
+
+    def backwards(self, orm):
+        return
+
+    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, 9, 18, 14, 56, 37, 729945)', '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, 9, 18, 14, 56, 37, 723535)'}),
+            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'estimated_participants': ('django.db.models.fields.PositiveIntegerField', [], {'null': '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']"}),
+            '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', [], {}),
+            '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', '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'}),
+            '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', '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.astakosuserquota': {
+            'Meta': {'unique_together': "(('resource', 'user'),)", 'object_name': 'AstakosUserQuota'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'limit': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            '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, 9, 18, 14, 56, 37, 731491)'}),
+            '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.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, 9, 18, 14, 56, 37, 727517)', '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.resource': {
+            'Meta': {'object_name': 'Resource'},
+            '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']"})
+        },
+        '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'})
+        }
+    }
+
+    complete_apps = ['im']
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
new file mode 100644 (file)
index 0000000..e4fe68a
--- /dev/null
@@ -0,0 +1,188 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+
+        # Changing field 'AstakosGroupQuota.limit'
+        db.alter_column('im_astakosgroupquota', 'limit', self.gf(
+            'django.db.models.fields.PositiveIntegerField')(null=True))
+
+        # Changing field 'AstakosUserQuota.limit'
+        db.alter_column('im_astakosuserquota', 'limit', self.gf(
+            'django.db.models.fields.PositiveIntegerField')(null=True))
+
+    def backwards(self, orm):
+        pass
+#        # 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))
+
+    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, 10, 2, 10, 33, 53, 42109)', '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, 10, 2, 10, 33, 53, 36319)'}),
+            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'estimated_participants': ('django.db.models.fields.PositiveIntegerField', [], {'null': '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']"}),
+            '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', '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'}),
+            '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': '0'}),
+            'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
+            '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', '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.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, 10, 2, 10, 33, 53, 43584)'}),
+            '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.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, 10, 2, 10, 33, 53, 40054)', '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.resource': {
+            'Meta': {'object_name': 'Resource'},
+            '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']"})
+        },
+        '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'})
+        }
+    }
+
+    complete_apps = ['im']
diff --git a/snf-astakos-app/astakos/im/migrations/0025_case_insensitive_emails.py b/snf-astakos-app/astakos/im/migrations/0025_case_insensitive_emails.py
new file mode 100644 (file)
index 0000000..9e530b6
--- /dev/null
@@ -0,0 +1,177 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+
+class Migration(DataMigration):
+
+    def forwards(self, orm):
+        "Write your forwards methods here."
+        for u in orm.AstakosUser.objects.all():
+            u.email = u.email.lower()
+            u.save()
+
+    def backwards(self, orm):
+        "Write your backwards methods here."
+
+    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, 10, 4, 9, 47, 13, 40029)', '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, 10, 4, 9, 47, 13, 34050)'}),
+            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'estimated_participants': ('django.db.models.fields.PositiveIntegerField', [], {'null': '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']"}),
+            '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', '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'}),
+            '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', '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.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, 10, 4, 9, 47, 13, 41566)'}),
+            '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.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, 10, 4, 9, 47, 13, 37772)', '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.resource': {
+            'Meta': {'object_name': 'Resource'},
+            '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']"})
+        },
+        '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'})
+        }
+    }
+
+    complete_apps = ['im']
diff --git a/snf-astakos-app/astakos/im/migrations/0026_auto__add_field_resource_desc__add_field_resource_unit.py b/snf-astakos-app/astakos/im/migrations/0026_auto__add_field_resource_desc__add_field_resource_unit.py
new file mode 100644 (file)
index 0000000..789da9e
--- /dev/null
@@ -0,0 +1,186 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+
+        # Adding field 'Resource.desc'
+        db.add_column('im_resource', 'desc', self.gf('django.db.models.fields.TextField')(null=True), keep_default=False)
+
+        # Adding field 'Resource.unit'
+        db.add_column('im_resource', 'unit', self.gf('django.db.models.fields.CharField')(max_length=255, null=True), keep_default=False)
+
+    def backwards(self, orm):
+
+        # Deleting field 'Resource.desc'
+        db.delete_column('im_resource', 'desc')
+
+        # Deleting field 'Resource.unit'
+        db.delete_column('im_resource', 'unit')
+
+    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, 10, 30, 16, 37, 5, 608037)', '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, 10, 30, 16, 37, 5, 601308)'}),
+            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'estimated_participants': ('django.db.models.fields.PositiveIntegerField', [], {'null': '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']"}),
+            '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', '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'}),
+            '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', '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.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, 10, 30, 16, 37, 5, 609676)'}),
+            '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.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, 10, 30, 16, 37, 5, 605488)', '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.resource': {
+            'Meta': {'object_name': 'Resource'},
+            'desc': ('django.db.models.fields.TextField', [], {'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'})
+        }
+    }
+
+    complete_apps = ['im']
diff --git a/snf-astakos-app/astakos/im/migrations/0027_auto__add_field_resource_group.py b/snf-astakos-app/astakos/im/migrations/0027_auto__add_field_resource_group.py
new file mode 100644 (file)
index 0000000..df847cf
--- /dev/null
@@ -0,0 +1,182 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding field 'Resource.group'
+        db.add_column('im_resource', 'group', self.gf('django.db.models.fields.CharField')(max_length=255, null=True), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # Deleting field 'Resource.group'
+        db.delete_column('im_resource', 'group')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        '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, 11, 5, 11, 38, 19, 866217)', '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, 11, 5, 11, 38, 19, 860127)'}),
+            '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']"}),
+            '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', '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'}),
+            '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', '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.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, 11, 5, 11, 38, 19, 867741)'}),
+            '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.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, 11, 5, 11, 38, 19, 863952)', '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.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'})
+        }
+    }
+
+    complete_apps = ['im']
diff --git a/snf-astakos-app/astakos/im/migrations/0028_auto__add_field_astakosuser_disturbed_quota.py b/snf-astakos-app/astakos/im/migrations/0028_auto__add_field_astakosuser_disturbed_quota.py
new file mode 100644 (file)
index 0000000..a157ad5
--- /dev/null
@@ -0,0 +1,183 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding field 'AstakosUser.disturbed_quota'
+        db.add_column('im_astakosuser', 'disturbed_quota', self.gf('django.db.models.fields.BooleanField')(default=False, db_index=True), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # Deleting field 'AstakosUser.disturbed_quota'
+        db.delete_column('im_astakosuser', 'disturbed_quota')
+
+
+    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, 11, 5, 11, 38, 41, 105536)', '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, 11, 5, 11, 38, 41, 99282)'}),
+            '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']"}),
+            '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', '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', '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.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, 11, 5, 11, 38, 41, 107071)'}),
+            '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.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, 11, 5, 11, 38, 41, 103264)', '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.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'})
+        }
+    }
+
+    complete_apps = ['im']
diff --git a/snf-astakos-app/astakos/im/migrations/0029_auto__add_field_astakosgroup_max_participants.py b/snf-astakos-app/astakos/im/migrations/0029_auto__add_field_astakosgroup_max_participants.py
new file mode 100644 (file)
index 0000000..0127126
--- /dev/null
@@ -0,0 +1,184 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding field 'AstakosGroup.max_participants'
+        db.add_column('im_astakosgroup', 'max_participants', self.gf('django.db.models.fields.PositiveIntegerField')(null=True, blank=True), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # Deleting field 'AstakosGroup.max_participants'
+        db.delete_column('im_astakosgroup', 'max_participants')
+
+
+    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, 11, 5, 11, 38, 52, 727224)', '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, 11, 5, 11, 38, 52, 720807)'}),
+            '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', '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', '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.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, 11, 5, 11, 38, 52, 728753)'}),
+            '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.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, 11, 5, 11, 38, 52, 724952)', '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.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'})
+        }
+    }
+
+    complete_apps = ['im']
diff --git a/snf-astakos-app/astakos/im/migrations/0030_populate_resource_data.py b/snf-astakos-app/astakos/im/migrations/0030_populate_resource_data.py
new file mode 100644 (file)
index 0000000..be4d738
--- /dev/null
@@ -0,0 +1,239 @@
+# 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, 11, 5, 12, 47, 39, 465319)', '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, 11, 5, 12, 47, 39, 456067)'}),
+            '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', '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', '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.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, 11, 5, 12, 47, 39, 466946)'}),
+            '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.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, 11, 5, 12, 47, 39, 462857)', '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.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'})
+        }
+    }
+
+    complete_apps = ['im']
diff --git a/snf-astakos-app/astakos/im/migrations/0031_populate_group_data.py b/snf-astakos-app/astakos/im/migrations/0031_populate_group_data.py
new file mode 100644 (file)
index 0000000..53bbd24
--- /dev/null
@@ -0,0 +1,236 @@
+# encoding: utf-8
+import datetime
+from south.v2 import DataMigration
+from django.db.models import Count
+
+
+class Migration(DataMigration):
+    def forwards(self, orm):
+
+        def _create_groupkind(name):
+            try:
+                orm.GroupKind(name=name).save()
+            except:
+                pass
+
+        t = ('default', 'course', 'project', 'laboratory', 'organization')
+        map(_create_groupkind, t)
+
+        default = orm.GroupKind.objects.get(name='default')
+
+        groups = {}
+
+        def _create_astakogroup(name):
+            try:
+                groups[name] = orm['im.AstakosGroup'].objects.get(name=name)
+            except orm.AstakosGroup.DoesNotExist:
+                try:
+                    g = orm['auth.Group'].objects.get(name=name)
+                    groups[
+                        name] = extended = orm.AstakosGroup(group_ptr_id=g.pk)
+                    extended.__dict__.update(g.__dict__)
+                    extended.kind = default
+                    extended.approval_date = datetime.datetime.now()
+                    extended.issue_date = datetime.datetime.now()
+                    extended.moderation_enabled = False
+                    extended.save()
+                    map(lambda u: orm.Membership(group=extended,
+                                                 person=orm.AstakosUser.objects.get(id=u.id),
+                                                 date_joined=datetime.datetime.now()
+                                                 ).save(),
+                        g.user_set.all())
+                except orm['auth.Group'].DoesNotExist:
+                    groups[name] = orm.AstakosGroup(name=name,
+                                                    kind=default,
+                                                    approval_date=datetime.datetime.now(),
+                                                    issue_date=datetime.datetime.now(),
+                                                    moderation_enabled=False
+                                                    )
+                    groups[name].save()
+
+        # catch integrate
+        t = ('default', 'shibboleth', 'helpdesk', 'faculty',
+             'ugrad', 'grad', 'researcher', 'associate')
+        map(_create_astakogroup, t)
+
+        orphans = orm.AstakosUser.objects.annotate(
+            num_groups=Count('astakos_groups')).filter(num_groups=0)
+        map(lambda u: orm.Membership(group=groups['default'],
+                                     person=u, date_joined=datetime.datetime.now()).save(), orphans)
+
+    def backwards(self, orm):
+        def _delete_groupkind(name):
+            try:
+                orm.GroupKind.objects.get(name=name).delete()
+            except orm.GroupKind.DoesNotExist:
+                pass
+
+        def _delete_astakosgroup(name):
+            try:
+                orm.AstakosGroup.objects.get(name=name).delete()
+            except orm.AstakosGroup.DoesNotExist:
+                pass
+
+        t = ('default', 'shibboleth', 'helpdesk', 'faculty',
+             'ugrad', 'grad', 'researcher', 'associate')
+        map(_delete_astakosgroup, t)
+
+        t = ('default', 'course', 'project', 'laboratory', 'organization')
+        map(_delete_groupkind, t)
+
+    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, 8, 8, 12, 40, 8, 181485)', '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, 8, 8, 12, 40, 8, 175548)'}),
+            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'estimated_participants': ('django.db.models.fields.PositiveIntegerField', [], {'null': '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'}),
+            'issue_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'kind': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.GroupKind']"}),
+            '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', [], {}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"})
+        },
+        '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', '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'}),
+            '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', '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.astakosuserquota': {
+            'Meta': {'unique_together': "(('resource', 'user'),)", 'object_name': 'AstakosUserQuota'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'limit': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}),
+            '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, 8, 8, 12, 40, 8, 183025)'}),
+            '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.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, 8, 8, 12, 40, 8, 179349)', '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.resource': {
+            'Meta': {'object_name': 'Resource'},
+            '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']"})
+        },
+        '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'})
+        }
+    }
+
+    complete_apps = ['im']
index 995c69d..546386c 100644 (file)
 import hashlib
 import uuid
 import logging
-import json
 
 from time import asctime
 from datetime import datetime, timedelta
 from base64 import b64encode
-from urlparse import urlparse
 from random import randint
+from collections import defaultdict
 
 from django.db import models, IntegrityError
-from django.contrib.auth.models import User, UserManager, Group
+from django.contrib.auth.models import User, UserManager, Group, Permission
 from django.utils.translation import ugettext as _
-from django.core.exceptions import ValidationError
-from django.template.loader import render_to_string
-from django.core.mail import send_mail
 from django.db import transaction
-from django.db.models.signals import post_save, pre_save, post_syncdb
+from django.core.exceptions import ValidationError
+from django.db.models.signals import (
+    pre_save, post_save, post_syncdb, post_delete
+)
+from django.contrib.contenttypes.models import ContentType
+
+from django.dispatch import Signal
 from django.db.models import Q
 from django.conf import settings
 from django.utils.importlib import import_module
 
-from astakos.im.settings import (
-    DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL,
-    AUTH_TOKEN_DURATION, BILLING_FIELDS, QUEUE_CONNECTION, SITENAME,
-    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
 )
+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
 
-QUEUE_CLIENT_ID = 3 # Astakos.
+import astakos.im.messages as astakos_messages
 
 logger = logging.getLogger(__name__)
 
+DEFAULT_CONTENT_TYPE = None
+try:
+    content_type = ContentType.objects.get(app_label='im', model='astakosuser')
+except:
+    content_type = DEFAULT_CONTENT_TYPE
+
+RESOURCE_SEPARATOR = '.'
+
+inf = float('inf')
+
+class Service(models.Model):
+    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,
+                                  null=True, blank=True)
+    auth_token_created = models.DateTimeField('Token creation date', null=True)
+    auth_token_expires = models.DateTimeField(
+        'Token expiration date', null=True)
+
+    def renew_token(self):
+        md5 = hashlib.md5()
+        md5.update(self.name.encode('ascii', 'ignore'))
+        md5.update(self.url.encode('ascii', 'ignore'))
+        md5.update(asctime())
+
+        self.auth_token = b64encode(md5.digest())
+        self.auth_token_created = datetime.now()
+        self.auth_token_expires = self.auth_token_created + \
+            timedelta(hours=AUTH_TOKEN_DURATION)
+
+    def __str__(self):
+        return self.name
+
+    @property
+    def resources(self):
+        return self.resource_set.all()
+
+    @resources.setter
+    def resources(self, resources):
+        for s in resources:
+            self.resource_set.create(**s)
+    
+    def add_resource(self, service, resource, uplimit, update=True):
+        """Raises ObjectDoesNotExist, IntegrityError"""
+        resource = Resource.objects.get(service__name=service, name=resource)
+        if update:
+            AstakosUserQuota.objects.update_or_create(user=self,
+                                                      resource=resource,
+                                                      defaults={'uplimit': uplimit})
+        else:
+            q = self.astakosuserquota_set
+            q.create(resource=resource, uplimit=uplimit)
+
+
+class ResourceMetadata(models.Model):
+    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)
+    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)
+
+    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)
+
+    def __str__(self):
+        return self.name
+
+
+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)
+    policy = models.ManyToManyField(
+        Resource,
+        null=True,
+        blank=True,
+        through='AstakosGroupQuota'
+    )
+    creation_date = models.DateTimeField(
+        'Creation date',
+        default=datetime.now()
+    )
+    issue_date = models.DateTimeField('Issue date', null=True)
+    expiration_date = models.DateTimeField(
+        'Expiration date',
+         null=True
+    )
+    moderation_enabled = models.BooleanField(
+        'Moderated membership?',
+        default=True
+    )
+    approval_date = models.DateTimeField(
+        'Activation date',
+        null=True,
+        blank=True
+    )
+    estimated_participants = models.PositiveIntegerField(
+        'Estimated #members',
+        null=True,
+        blank=True,
+    )
+    max_participants = models.PositiveIntegerField(
+        'Maximum numder of participants',
+        null=True,
+        blank=True
+    )
+    
+    @property
+    def is_disabled(self):
+        if not self.approval_date:
+            return True
+        return False
+
+    @property
+    def is_enabled(self):
+        if self.is_disabled:
+            return False
+        if not self.issue_date:
+            return False
+        if not self.expiration_date:
+            return True
+        now = datetime.now()
+        if self.issue_date > now:
+            return False
+        if now >= self.expiration_date:
+            return False
+        return True
+
+    def enable(self):
+        if self.is_enabled:
+            return
+        self.approval_date = datetime.now()
+        self.save()
+        quota_disturbed.send(sender=self, users=self.approved_members)
+        propagate_groupmembers_quota.apply_async(
+            args=[self], eta=self.issue_date)
+        propagate_groupmembers_quota.apply_async(
+            args=[self], eta=self.expiration_date)
+
+    def disable(self):
+        if self.is_disabled:
+            return
+        self.approval_date = None
+        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)
+
+    @property
+    def members(self):
+        q = self.membership_set.select_related().all()
+        return [m.person for m in q]
+    
+    @property
+    def approved_members(self):
+        q = self.membership_set.select_related().all()
+        return [m.person for m in q if m.is_approved]
+
+    @property
+    def quota(self):
+        d = defaultdict(int)
+        for q in self.astakosgroupquota_set.select_related().all():
+            d[q.resource] += q.uplimit or inf
+        return d
+    
+    def add_policy(self, service, resource, uplimit, update=True):
+        """Raises ObjectDoesNotExist, IntegrityError"""
+        resource = Resource.objects.get(service__name=service, name=resource)
+        if update:
+            AstakosGroupQuota.objects.update_or_create(
+                group=self,
+                resource=resource,
+                defaults={'uplimit': uplimit}
+            )
+        else:
+            q = self.astakosgroupquota_set
+            q.create(resource=resource, uplimit=uplimit)
+    
+    @property
+    def policies(self):
+        return self.astakosgroupquota_set.select_related().all()
+
+    @policies.setter
+    def 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_policy(service, resource, uplimit, update)
+    
+    @property
+    def owners(self):
+        return self.owner.all()
+
+    @property
+    def owner_details(self):
+        return self.owner.select_related().all()
+
+    @owners.setter
+    def owners(self, l):
+        self.owner = l
+        map(self.approve_member, l)
+
+
 class AstakosUser(User):
     """
     Extends ``django.contrib.auth.models.User`` by defining additional fields.
@@ -77,44 +312,60 @@ class AstakosUser(User):
     #for invitations
     user_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 = models.IntegerField(
+        'Invitations left', default=INVITATIONS_PER_LEVEL.get(user_level, 0))
 
     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_expires = models.DateTimeField('Token expiration date', null=True)
+    auth_token_expires = models.DateTimeField(
+        'Token expiration date', null=True)
 
     updated = models.DateTimeField('Update date')
     is_verified = models.BooleanField('Is verified?', default=False)
 
     # ex. screen_name for twitter, eppn for shibboleth
-    third_party_identifier = models.CharField('Third-party identifier', max_length=255, null=True, blank=True)
+    third_party_identifier = models.CharField(
+        'Third-party identifier', max_length=255, null=True, blank=True)
 
     email_verified = models.BooleanField('Email verified?', default=False)
 
     has_credits = models.BooleanField('Has credits?', default=False)
-    has_signed_terms = models.BooleanField('Agree with the terms?', default=False)
-    date_signed_terms = models.DateTimeField('Signed terms date', null=True, blank=True)
-    
-    activation_sent = models.DateTimeField('Activation sent data', null=True, blank=True)
-    
+    has_signed_terms = models.BooleanField(
+        'I agree with the terms', default=False)
+    date_signed_terms = models.DateTimeField(
+        'Signed terms date', null=True, blank=True)
+
+    activation_sent = models.DateTimeField(
+        'Activation sent data', null=True, blank=True)
+
+    policy = models.ManyToManyField(
+        Resource, null=True, through='AstakosUserQuota')
+
+    astakos_groups = models.ManyToManyField(
+        AstakosGroup, verbose_name=_('agroups'), blank=True,
+        help_text=_(astakos_messages.ASTAKOSUSER_GROUPS_HELP),
+        through='Membership')
+
     __has_signed_terms = False
-    __groupnames = []
-    
+    disturbed_quota = models.BooleanField('Needs quotaholder syncing',
+                                           default=False, db_index=True)
+
+    owner = models.ManyToManyField(
+        AstakosGroup, related_name='owner', null=True)
+
     class Meta:
         unique_together = ("provider", "third_party_identifier")
-    
+
     def __init__(self, *args, **kwargs):
         super(AstakosUser, self).__init__(*args, **kwargs)
         self.__has_signed_terms = self.has_signed_terms
-        if self.id:
-            self.__groupnames = [g.name for g in self.groups.all()]
-        else:
+        if not self.id:
             self.is_active = False
-    
+
     @property
     def realname(self):
-        return '%s %s' %(self.first_name, self.last_name)
+        return '%s %s' % (self.first_name, self.last_name)
 
     @realname.setter
     def realname(self, value):
@@ -125,6 +376,21 @@ class AstakosUser(User):
         else:
             self.last_name = parts[0]
 
+    def add_permission(self, pname):
+        if self.has_perm(pname):
+            return
+        p, created = Permission.objects.get_or_create(codename=pname,
+                                                      name=pname.capitalize(),
+                                                      content_type=content_type)
+        self.user_permissions.add(p)
+
+    def remove_permission(self, pname):
+        if self.has_perm(pname):
+            return
+        p = Permission.objects.get(codename=pname,
+                                   content_type=content_type)
+        self.user_permissions.remove(p)
+
     @property
     def invitation(self):
         try:
@@ -132,41 +398,97 @@ class AstakosUser(User):
         except Invitation.DoesNotExist:
             return None
 
+    def invite(self, email, realname):
+        inv = Invitation(inviter=self, username=email, realname=realname)
+        inv.save()
+        send_invitation(inv)
+        self.invitations = max(0, self.invitations - 1)
+        self.save()
+
+    @property
+    def quota(self):
+        """Returns a dict with the sum of quota limits per resource"""
+        d = defaultdict(int)
+        for q in self.policies:
+            d[q.resource] += q.uplimit or inf
+        for m in self.extended_groups:
+            if not m.is_approved:
+                continue
+            g = m.group
+            if not g.is_enabled:
+                continue
+            for r, uplimit in g.quota.iteritems():
+                d[r] += uplimit or inf
+        # TODO set default for remaining
+        return d
+
+    @property
+    def policies(self):
+        return self.astakosuserquota_set.select_related().all()
+
+    @policies.setter
+    def policies(self, policies):
+        for p in policies:
+            service = policies.get('service', None)
+            resource = policies.get('resource', None)
+            uplimit = policies.get('uplimit', 0)
+            update = policies.get('update', True)
+            self.add_policy(service, resource, uplimit, update)
+
+    def add_policy(self, service, resource, uplimit, update=True):
+        """Raises ObjectDoesNotExist, IntegrityError"""
+        resource = Resource.objects.get(service__name=service, name=resource)
+        if update:
+            AstakosUserQuota.objects.update_or_create(user=self,
+                                                      resource=resource,
+                                                      defaults={'uplimit': uplimit})
+        else:
+            q = self.astakosuserquota_set
+            q.create(resource=resource, uplimit=uplimit)
+
+    def remove_policy(self, service, resource):
+        """Raises ObjectDoesNotExist, IntegrityError"""
+        resource = Resource.objects.get(service__name=service, name=resource)
+        q = self.policies.get(resource=resource).delete()
+
+    @property
+    def extended_groups(self):
+        return self.membership_set.select_related().all()
+
+    @extended_groups.setter
+    def extended_groups(self, groups):
+        #TODO exceptions
+        for name in (groups or ()):
+            group = AstakosGroup.objects.get(name=name)
+            self.membership_set.create(group=group)
+
     def save(self, update_timestamps=True, **kwargs):
         if update_timestamps:
             if not self.id:
                 self.date_joined = datetime.now()
             self.updated = datetime.now()
-        
+
         # update date_signed_terms if necessary
         if self.__has_signed_terms != self.has_signed_terms:
             self.date_signed_terms = datetime.now()
-        
+
         if not self.id:
             # set username
             while not self.username:
-                username =  uuid.uuid4().hex[:30]
+                username = uuid.uuid4().hex[:30]
                 try:
-                    AstakosUser.objects.get(username = username)
-                except AstakosUser.DoesNotExist, e:
+                    AstakosUser.objects.get(username=username)
+                except AstakosUser.DoesNotExist:
                     self.username = username
             if not self.provider:
                 self.provider = 'local'
-        report_user_event(self)
+            self.email = self.email.lower()
         self.validate_unique_email_isactive()
         if self.is_active and self.activation_sent:
             # reset the activation sent
             self.activation_sent = None
+
         super(AstakosUser, self).save(**kwargs)
-        
-        # set default group if does not exist
-        groupname = 'default'
-        if groupname not in self.__groupnames:
-            try:
-                group = Group.objects.get(name = groupname)
-                self.groups.add(group)
-            except Group.DoesNotExist, e:
-                logger.exception(e)
     
     def renew_token(self, flush_sessions=False, current_key=None):
         md5 = hashlib.md5()
@@ -182,7 +504,7 @@ class AstakosUser(User):
         if flush_sessions:
             self.flush_sessions(current_key)
         msg = 'Token renewed for %s' % self.email
-        logger._log(LOGGING_LEVEL, msg, [])
+        logger.log(LOGGING_LEVEL, msg)
 
     def flush_sessions(self, current_key=None):
         q = self.sessions
@@ -192,22 +514,22 @@ class AstakosUser(User):
         keys = q.values_list('session_key', flat=True)
         if keys:
             msg = 'Flushing sessions: %s' % ','.join(keys)
-            logger._log(LOGGING_LEVEL, msg, [])
+            logger.log(LOGGING_LEVEL, msg, [])
         engine = import_module(settings.SESSION_ENGINE)
         for k in keys:
             s = engine.SessionStore(k)
             s.flush()
 
     def __unicode__(self):
-        return self.username
-    
+        return '%s (%s)' % (self.realname, self.email)
+
     def conflicting_email(self):
-        q = AstakosUser.objects.exclude(username = self.username)
-        q = q.filter(email = self.email)
+        q = AstakosUser.objects.exclude(username=self.username)
+        q = q.filter(email=self.email)
         if q.count() != 0:
             return True
         return False
-    
+
     def validate_unique_email_isactive(self):
         """
         Implements a unique_together constraint for email and is_active fields.
@@ -218,8 +540,9 @@ class AstakosUser(User):
         if self.id:
             q = q.filter(~Q(id = self.id))
         if q.count() != 0:
-            raise ValidationError({'__all__':[_('Another account with the same email & is_active combination found.')]})
-    
+            raise ValidationError({'__all__': [_(astakos_messages.UNIQUE_EMAIL_IS_ACTIVE_CONSTRAIN_ERR)]})
+
+    @property
     def signed_terms(self):
         term = get_latest_terms()
         if not term:
@@ -235,14 +558,106 @@ class AstakosUser(User):
             return False
         return True
 
+    def store_disturbed_quota(self, set=True):
+        self.disturbed_quota = set
+        self.save()
+
+
+class Membership(models.Model):
+    person = models.ForeignKey(AstakosUser)
+    group = models.ForeignKey(AstakosGroup)
+    date_requested = models.DateField(default=datetime.now(), blank=True)
+    date_joined = models.DateField(null=True, db_index=True, blank=True)
+
+    class Meta:
+        unique_together = ("person", "group")
+
+    def save(self, *args, **kwargs):
+        if not self.id:
+            if not self.group.moderation_enabled:
+                self.date_joined = datetime.now()
+        super(Membership, self).save(*args, **kwargs)
+
+    @property
+    def is_approved(self):
+        if self.date_joined:
+            return True
+        return False
+
+    def approve(self):
+        if self.is_approved:
+            return
+        if self.group.max_participants:
+            assert len(self.group.approved_members) + 1 <= self.group.max_participants, \
+            'Maximum participant number has been reached.'
+        self.date_joined = datetime.now()
+        self.save()
+        quota_disturbed.send(sender=self, users=(self.person,))
+
+    def disapprove(self):
+        self.delete()
+        quota_disturbed.send(sender=self, users=(self.person,))
+
+class AstakosQuotaManager(models.Manager):
+    def _update_or_create(self, **kwargs):
+        assert kwargs, \
+            'update_or_create() must be passed at least one keyword argument'
+        obj, created = self.get_or_create(**kwargs)
+        defaults = kwargs.pop('defaults', {})
+        if created:
+            return obj, True, False
+        else:
+            try:
+                params = dict(
+                    [(k, v) for k, v in kwargs.items() if '__' not in k])
+                params.update(defaults)
+                for attr, val in params.items():
+                    if hasattr(obj, attr):
+                        setattr(obj, attr, val)
+                sid = transaction.savepoint()
+                obj.save(force_update=True)
+                transaction.savepoint_commit(sid)
+                return obj, False, True
+            except IntegrityError, e:
+                transaction.savepoint_rollback(sid)
+                try:
+                    return self.get(**kwargs), False, False
+                except self.model.DoesNotExist:
+                    raise e
+
+    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)
+    resource = models.ForeignKey(Resource)
+    group = models.ForeignKey(AstakosGroup, blank=True)
+
+    class Meta:
+        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)
+    resource = models.ForeignKey(Resource)
+    user = models.ForeignKey(AstakosUser)
+
+    class Meta:
+        unique_together = ("resource", "user")
+
+
 class ApprovalTerms(models.Model):
     """
     Model for approval terms
     """
 
-    date = models.DateTimeField('Issue date', db_index=True, default=datetime.now())
+    date = models.DateTimeField(
+        'Issue date', db_index=True, default=datetime.now())
     location = models.CharField('Terms location', max_length=255)
 
+
 class Invitation(models.Model):
     """
     Model for registring invitations
@@ -255,12 +670,12 @@ class Invitation(models.Model):
     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)
         if not self.id:
             self.code = _generate_invitation_code()
-    
+
     def consume(self):
         self.is_consumed = True
         self.consumed = datetime.now()
@@ -269,49 +684,6 @@ class Invitation(models.Model):
     def __unicode__(self):
         return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
 
-def report_user_event(user):
-    def should_send(user):
-        # report event incase of new user instance
-        # or if specific fields are modified
-        if not user.id:
-            return True
-        db_instance = AstakosUser.objects.get(id = user.id)
-        for f in BILLING_FIELDS:
-            if (db_instance.__getattribute__(f) != user.__getattribute__(f)):
-                return True
-        return False
-
-    if QUEUE_CONNECTION and should_send(user):
-
-        from astakos.im.queue.userevent import UserEvent
-        from synnefo.lib.queue import exchange_connect, exchange_send, \
-                exchange_close
-
-        eventType = 'create' if not user.id else 'modify'
-        body = UserEvent(QUEUE_CLIENT_ID, user, eventType, {}).format()
-        conn = exchange_connect(QUEUE_CONNECTION)
-        parts = urlparse(QUEUE_CONNECTION)
-        exchange = parts.path[1:]
-        routing_key = '%s.user' % exchange
-        exchange_send(conn, routing_key, body)
-        exchange_close(conn)
-
-def _generate_invitation_code():
-    while True:
-        code = randint(1, 2L**63 - 1)
-        try:
-            Invitation.objects.get(code=code)
-            # An invitation with this code already exists, try again
-        except Invitation.DoesNotExist:
-            return code
-
-def get_latest_terms():
-    try:
-        term = ApprovalTerms.objects.order_by('-id')[0]
-        return term
-    except IndexError:
-        pass
-    return None
 
 class EmailChangeManager(models.Manager):
     @transaction.commit_on_success
@@ -333,7 +705,8 @@ class EmailChangeManager(models.Manager):
         Throws ValueError if there is already
         """
         try:
-            email_change = self.model.objects.get(activation_key=activation_key)
+            email_change = self.model.objects.get(
+                activation_key=activation_key)
             if email_change.activation_key_expired():
                 email_change.delete()
                 raise EmailChange.DoesNotExist
@@ -343,7 +716,7 @@ class EmailChangeManager(models.Manager):
             except AstakosUser.DoesNotExist:
                 pass
             else:
-                raise ValueError(_('The new email address is reserved.'))
+                raise ValueError(_(astakos_messages.NEW_EMAIL_ADDR_RESERVED))
             # update user
             user = AstakosUser.objects.get(pk=email_change.user_id)
             user.email = email_change.new_email_address
@@ -351,13 +724,17 @@ class EmailChangeManager(models.Manager):
             email_change.delete()
             return user
         except EmailChange.DoesNotExist:
-            raise ValueError(_('Invalid activation key'))
+            raise ValueError(_(astakos_messages.INVALID_ACTIVATION_KEY))
+
 
 class EmailChange(models.Model):
-    new_email_address = models.EmailField(_(u'new e-mail address'), help_text=_(u'Your old email address will be used until you verify your new one.'))
-    user = models.ForeignKey(AstakosUser, unique=True, related_name='emailchange_user')
+    new_email_address = models.EmailField(_(u'new e-mail address'),
+                                          help_text=_(astakos_messages.EMAIL_CHANGE_NEW_ADDR_HELP))
+    user = models.ForeignKey(
+        AstakosUser, unique=True, related_name='emailchange_user')
     requested_at = models.DateTimeField(default=datetime.now())
-    activation_key = models.CharField(max_length=40, unique=True, db_index=True)
+    activation_key = models.CharField(
+        max_length=40, unique=True, db_index=True)
 
     objects = EmailChangeManager()
 
@@ -365,28 +742,6 @@ class EmailChange(models.Model):
         expiration_date = timedelta(days=EMAILCHANGE_ACTIVATION_DAYS)
         return self.requested_at + expiration_date < datetime.now()
 
-class Service(models.Model):
-    name = models.CharField('Name', max_length=255, unique=True)
-    url = models.FilePathField()
-    icon = models.FilePathField(blank=True)
-    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_expires = models.DateTimeField('Token expiration date', null=True)
-    
-    def renew_token(self):
-        md5 = hashlib.md5()
-        md5.update(settings.SECRET_KEY)
-        md5.update(self.name.encode('ascii', 'ignore'))
-        md5.update(self.url.encode('ascii', 'ignore'))
-        md5.update(asctime())
-
-        self.auth_token = b64encode(md5.digest())
-        self.auth_token_created = datetime.now()
-        self.auth_token_expires = self.auth_token_created + \
-                                  timedelta(hours=AUTH_TOKEN_DURATION)
-        msg = 'Token renewed for %s' % self.name
-        logger._log(LOGGING_LEVEL, msg, [])
 
 class AdditionalMail(models.Model):
     """
@@ -395,6 +750,25 @@ class AdditionalMail(models.Model):
     owner = models.ForeignKey(AstakosUser)
     email = models.EmailField()
 
+
+def _generate_invitation_code():
+    while True:
+        code = randint(1, 2L ** 63 - 1)
+        try:
+            Invitation.objects.get(code=code)
+            # An invitation with this code already exists, try again
+        except Invitation.DoesNotExist:
+            return code
+
+
+def get_latest_terms():
+    try:
+        term = ApprovalTerms.objects.order_by('-id')[0]
+        return term
+    except IndexError:
+        pass
+    return None
+
 class PendingThirdPartyUser(models.Model):
     """
     Model for registring successful third party user authentications
@@ -438,6 +812,7 @@ class SessionCatalog(models.Model):
     session_key = models.CharField(_('session key'), max_length=40)
     user = models.ForeignKey(AstakosUser, related_name='sessions', null=True)
 
+
 def create_astakos_user(u):
     try:
         AstakosUser.objects.get(user_ptr=u.pk)
@@ -445,30 +820,109 @@ def create_astakos_user(u):
         extended_user = AstakosUser(user_ptr_id=u.pk)
         extended_user.__dict__.update(u.__dict__)
         extended_user.save()
-    except:
-        pass
+    except BaseException, e:
+        logger.exception(e)
+
 
-def superuser_post_syncdb(sender, **kwargs):
-    # if there was created a superuser
-    # associate it with an AstakosUser
+def fix_superusers(sender, **kwargs):
+    # Associate superusers with AstakosUser
     admins = User.objects.filter(is_superuser=True)
     for u in admins:
         create_astakos_user(u)
 
-post_syncdb.connect(superuser_post_syncdb)
 
-def superuser_post_save(sender, instance, **kwargs):
-    if instance.is_superuser:
-        create_astakos_user(instance)
+def user_post_save(sender, instance, created, **kwargs):
+    if not created:
+        return
+    create_astakos_user(instance)
 
-def astakosuser_post_save(sender, instance, **kwargs):
-    pass
 
-post_save.connect(superuser_post_save, sender=User)
+def set_default_group(user):
+    try:
+        default = AstakosGroup.objects.get(name='default')
+        Membership(
+            group=default, person=user, date_joined=datetime.now()).save()
+    except AstakosGroup.DoesNotExist, e:
+        logger.exception(e)
+
+
+def astakosuser_pre_save(sender, instance, **kwargs):
+    instance.aquarium_report = False
+    instance.new = False
+    try:
+        db_instance = AstakosUser.objects.get(id=instance.id)
+    except AstakosUser.DoesNotExist:
+        # create event
+        instance.aquarium_report = True
+        instance.new = True
+    else:
+        get = AstakosUser.__getattribute__
+        l = filter(lambda f: get(db_instance, f) != get(instance, f),
+                   BILLING_FIELDS)
+        instance.aquarium_report = True if l else False
+
+
+def astakosuser_post_save(sender, instance, created, **kwargs):
+    if instance.aquarium_report:
+        report_user_event(instance, create=instance.new)
+    if not created:
+        return
+    set_default_group(instance)
+    # TODO handle socket.error & IOError
+    register_users((instance,))
+    instance.renew_token()
+
+
+def resource_post_save(sender, instance, created, **kwargs):
+    if not created:
+        return
+    register_resources((instance,))
+
+
+def send_quota_disturbed(sender, instance, **kwargs):
+    users = []
+    extend = users.extend
+    if sender == Membership:
+        if not instance.group.is_enabled:
+            return
+        extend([instance.person])
+    elif sender == AstakosUserQuota:
+        extend([instance.user])
+    elif sender == AstakosGroupQuota:
+        if not instance.group.is_enabled:
+            return
+        extend(instance.group.astakosuser_set.all())
+    elif sender == AstakosGroup:
+        if not instance.is_enabled:
+            return
+    quota_disturbed.send(sender=sender, users=users)
+
+
+def on_quota_disturbed(sender, users, **kwargs):
+#     print '>>>', locals()
+    if not users:
+        return
+    send_quota(users)
 
 def renew_token(sender, instance, **kwargs):
     if not instance.id:
         instance.renew_token()
 
+post_syncdb.connect(fix_superusers)
+post_save.connect(user_post_save, sender=User)
+pre_save.connect(astakosuser_pre_save, sender=AstakosUser)
+post_save.connect(astakosuser_post_save, sender=AstakosUser)
+post_save.connect(resource_post_save, sender=Resource)
+
+quota_disturbed = Signal(providing_args=["users"])
+quota_disturbed.connect(on_quota_disturbed)
+
+post_delete.connect(send_quota_disturbed, sender=AstakosGroup)
+post_delete.connect(send_quota_disturbed, sender=Membership)
+post_save.connect(send_quota_disturbed, sender=AstakosUserQuota)
+post_delete.connect(send_quota_disturbed, sender=AstakosUserQuota)
+post_save.connect(send_quota_disturbed, sender=AstakosGroupQuota)
+post_delete.connect(send_quota_disturbed, sender=AstakosGroupQuota)
+
 pre_save.connect(renew_token, sender=AstakosUser)
 pre_save.connect(renew_token, sender=Service)
\ No newline at end of file
index d7b5953..3fef584 100644 (file)
@@ -11,17 +11,16 @@ TWITTER_SECRET = getattr(settings, 'ASTAKOS_TWITTER_SECRET', '')
 DEFAULT_USER_LEVEL = getattr(settings, 'ASTAKOS_DEFAULT_USER_LEVEL', 4)
 
 INVITATIONS_PER_LEVEL = getattr(settings, 'ASTAKOS_INVITATIONS_PER_LEVEL', {
-    0   :   100,
-    1   :   2,
-    2   :   0,
-    3   :   0,
-    4   :   0
+    0: 100,
+    1: 2,
+    2: 0,
+    3: 0,
+    4: 0
 })
 
 # Address to use for outgoing emails
-DEFAULT_FROM_EMAIL = getattr(settings, 'ASTAKOS_DEFAULT_FROM_EMAIL', 'GRNET Cloud <no-reply@grnet.gr>')
-DEFAULT_CONTACT_EMAIL = getattr(settings, 'ASTAKOS_DEFAULT_CONTACT_EMAIL', 'support@cloud.grnet.gr')
-DEFAULT_ADMIN_EMAIL = getattr(settings, 'ASTAKOS_DEFAULT_ADMIN_EMAIL', 'support@cloud.grnet.gr')
+DEFAULT_CONTACT_EMAIL = getattr(
+    settings, 'ASTAKOS_DEFAULT_CONTACT_EMAIL', 'support@cloud.grnet.gr')
 
 # Identity Management enabled modules
 IM_MODULES = getattr(settings, 'ASTAKOS_IM_MODULES', ['local', 'shibboleth'])
@@ -51,7 +50,7 @@ SITENAME = getattr(settings, 'ASTAKOS_SITENAME', 'GRNET Cloud')
 RECAPTCHA_PUBLIC_KEY = getattr(settings, 'ASTAKOS_RECAPTCHA_PUBLIC_KEY', '')
 RECAPTCHA_PRIVATE_KEY = getattr(settings, 'ASTAKOS_RECAPTCHA_PRIVATE_KEY', '')
 RECAPTCHA_OPTIONS = getattr(settings, 'ASTAKOS_RECAPTCHA_OPTIONS',
-                            {'theme' : 'custom', 'custom_theme_widget': 'okeanos_recaptcha'})
+                            {'theme': 'custom', 'custom_theme_widget': 'okeanos_recaptcha'})
 RECAPTCHA_USE_SSL = getattr(settings, 'ASTAKOS_RECAPTCHA_USE_SSL', True)
 RECAPTCHA_ENABLED = getattr(settings, 'ASTAKOS_RECAPTCHA_ENABLED', True)
 
@@ -59,13 +58,14 @@ RECAPTCHA_ENABLED = getattr(settings, 'ASTAKOS_RECAPTCHA_ENABLED', True)
 BILLING_FIELDS = getattr(settings, 'ASTAKOS_BILLING_FIELDS', ['is_active'])
 
 # Queue for billing.
-QUEUE_CONNECTION = getattr(settings, 'ASTAKOS_QUEUE_CONNECTION', None) # Example: 'rabbitmq://guest:guest@localhost:5672/astakos'
+QUEUE_CONNECTION = getattr(settings, 'ASTAKOS_QUEUE_CONNECTION', None)  # Example: 'rabbitmq://guest:guest@localhost:5672/astakos'
 
 # Set where the user should be redirected after logout
 LOGOUT_NEXT = getattr(settings, 'ASTAKOS_LOGOUT_NEXT', '')
 
 # Set user email patterns that are automatically activated
-RE_USER_EMAIL_PATTERNS = getattr(settings, 'ASTAKOS_RE_USER_EMAIL_PATTERNS', [])
+RE_USER_EMAIL_PATTERNS = getattr(
+    settings, 'ASTAKOS_RE_USER_EMAIL_PATTERNS', [])
 
 # Messages to display on login page header
 # e.g. {'warning': 'This warning message will be displayed on the top of login page'}
@@ -87,39 +87,170 @@ GLOBAL_MESSAGES = getattr(settings, 'ASTAKOS_GLOBAL_MESSAGES', [])
 # e.g. {'https://cms.okeanos.grnet.gr/': 'Back to ~okeanos'}
 PROFILE_EXTRA_LINKS = getattr(settings, 'ASTAKOS_PROFILE_EXTRA_LINKS', {})
 
-# The number of unsuccessful login requests per minute allowed for a specific email
-RATELIMIT_RETRIES_ALLOWED = getattr(settings, 'ASTAKOS_RATELIMIT_RETRIES_ALLOWED', 3)
+# The number of unsuccessful login requests per minute allowed for a specific user
+RATELIMIT_RETRIES_ALLOWED = getattr(
+    settings, 'ASTAKOS_RATELIMIT_RETRIES_ALLOWED', 3)
 
 # If False the email change mechanism is disabled
 EMAILCHANGE_ENABLED = getattr(settings, 'ASTAKOS_EMAILCHANGE_ENABLED', False)
 
 # Set the expiration time (in days) of email change requests
-EMAILCHANGE_ACTIVATION_DAYS = getattr(settings, 'ASTAKOS_EMAILCHANGE_ACTIVATION_DAYS', 10)
+EMAILCHANGE_ACTIVATION_DAYS = getattr(
+    settings, 'ASTAKOS_EMAILCHANGE_ACTIVATION_DAYS', 10)
 
 # Set the astakos main functions logging severity (None to disable)
 from logging import INFO
 LOGGING_LEVEL = getattr(settings, 'ASTAKOS_LOGGING_LEVEL', INFO)
 
 # Configurable email subjects
-INVITATION_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_INVITATION_EMAIL_SUBJECT',
-        'Invitation to %s alpha2 testing' % SITENAME)
+INVITATION_EMAIL_SUBJECT = getattr(
+    settings, 'ASTAKOS_INVITATION_EMAIL_SUBJECT',
+    'Invitation to %s alpha2 testing' % SITENAME)
 GREETING_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_GREETING_EMAIL_SUBJECT',
-        'Welcome to %s alpha2 testing' % SITENAME)
+                                 'Welcome to %s alpha2 testing' % SITENAME)
 FEEDBACK_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_FEEDBACK_EMAIL_SUBJECT',
-        'Feedback from %s alpha2 testing' % SITENAME)
-VERIFICATION_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_VERIFICATION_EMAIL_SUBJECT',
-        '%s alpha2 testing account activation is needed' % SITENAME)
-ADMIN_NOTIFICATION_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_ADMIN_NOTIFICATION_EMAIL_SUBJECT',
-        '%s alpha2 testing account created (%%(user)s)' % SITENAME)
-HELPDESK_NOTIFICATION_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_HELPDESK_NOTIFICATION_EMAIL_SUBJECT',
-        '%s alpha2 testing account activated (%%(user)s)' % SITENAME)
-EMAIL_CHANGE_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_EMAIL_CHANGE_EMAIL_SUBJECT',
-        'Email change on %s alpha2 testing' % SITENAME)
-PASSWORD_RESET_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_PASSWORD_RESET_EMAIL_SUBJECT',
-        'Password reset on %s alpha2 testing' % SITENAME)
+                                 'Feedback from %s alpha2 testing' % SITENAME)
+VERIFICATION_EMAIL_SUBJECT = getattr(
+    settings, 'ASTAKOS_VERIFICATION_EMAIL_SUBJECT',
+    '%s alpha2 testing account activation is needed' % SITENAME)
+ACCOUNT_CREATION_SUBJECT = getattr(
+    settings, 'ASTAKOS_ACCOUNT_CREATION_SUBJECT',
+    '%s alpha2 testing account created (%%(user)s)' % SITENAME)
+GROUP_CREATION_SUBJECT = getattr(settings, 'ASTAKOS_GROUP_CREATION_SUBJECT',
+                                 '%s alpha2 testing group created (%%(group)s)' % SITENAME)
+HELPDESK_NOTIFICATION_EMAIL_SUBJECT = getattr(
+    settings, 'ASTAKOS_HELPDESK_NOTIFICATION_EMAIL_SUBJECT',
+    '%s alpha2 testing account activated (%%(user)s)' % SITENAME)
+EMAIL_CHANGE_EMAIL_SUBJECT = getattr(
+    settings, 'ASTAKOS_EMAIL_CHANGE_EMAIL_SUBJECT',
+    'Email change on %s alpha2 testing' % SITENAME)
+PASSWORD_RESET_EMAIL_SUBJECT = getattr(
+    settings, 'ASTAKOS_PASSWORD_RESET_EMAIL_SUBJECT',
+    'Password reset on %s alpha2 testing' % SITENAME)
+
+# Set the quota holder component URI
+QUOTA_HOLDER_URL = getattr(settings, 'ASTAKOS_QUOTA_HOLDER_URL', '')
+
+# Set the cloud service properties
+SERVICES = getattr(settings, 'ASTAKOS_SERVICES', {
+    'cyclades': {
+        'url': 'https://node1.example.com/ui/',
+        'resources': [{
+            'name':'vm',
+            'group':'compute',
+            'uplimit':2,
+            'desc': 'Number of virtual machines'
+            },{
+            'name':'disk',
+            'group':'compute',
+            'uplimit':30*1024*1024*1024,
+            'unit':'bytes',
+            'desc': 'Virtual machine disk size'
+            },{
+            'name':'cpu',
+            'group':'compute',
+            'uplimit':6,
+            'desc': 'Number of virtual machine processors'
+            },{
+            'name':'ram',
+            'group':'compute',
+            'uplimit':6*1024*1024*1024,
+            'unit':'bytes',
+            'desc': 'Virtual machines'
+            },{
+            'name':'network.private',
+            'group':'network',
+            'uplimit':1,
+            'desc': 'Private networks'
+            }
+        ]
+    },
+    'pithos+': {
+        'url': 'https://node2.example.com/ui/',
+        'resources':[{
+            'name':'diskspace',
+            'group':'storage',
+            'uplimit':5 * 1024 * 1024 * 1024,
+            'unit':'bytes',
+            'desc': 'Pithos account diskspace'
+            }]
+    }
+})
+
+# Set the billing URI
+AQUARIUM_URL = getattr(settings, 'ASTAKOS_AQUARIUM_URL', '')
+
+# Set how many objects should be displayed per page
+PAGINATE_BY = getattr(settings, 'ASTAKOS_PAGINATE_BY', 8)
 
 # Enforce token renewal on password change/reset
-NEWPASSWD_INVALIDATE_TOKEN = getattr(settings, 'ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN', True)
+NEWPASSWD_INVALIDATE_TOKEN = getattr(
+    settings, 'ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN', True)
+
+
+RESOURCES_PRESENTATION_DATA = getattr(
+    settings, 'ASTAKOS_RESOURCES_PRESENTATION_DATA', {
+        'groups': {
+             'compute': {
+                'help_text':'group compute help text',
+                'is_abbreviation':False,
+                'report_desc':'',
+                 'verbose_name':'compute', 
+            },
+            'storage': {
+                'help_text':'group storage help text',
+                'is_abbreviation':False,
+                'report_desc':'',
+                 'verbose_name':'storage', 
+            },
+        },
+        'resources': {
+            'pithos+.diskspace': {
+                'help_text':'resource pithos+.diskspace help text',
+                'is_abbreviation':False,
+                'report_desc':'Pithos+ Diskspace',
+                'placeholder':'eg. 10GB',
+                'verbose_name':'diskspace', 
+            },
+            'cyclades.vm': {
+                'help_text':'resource cyclades.vm help text resource cyclades.vm help text resource cyclades.vm help text resource cyclades.vm help text',
+                'is_abbreviation':True,
+                'report_desc':'Virtual Machines',
+                'placeholder':'eg. 2',
+                'verbose_name':'vm', 
+            },
+            'cyclades.disk': {
+                'help_text':'resource cyclades.disk help text',
+                'is_abbreviation':False,
+                'report_desc':'Disk',
+                'placeholder':'eg. 5GB, 2GB etc',
+                'verbose_name':'disk'
+            },
+            'cyclades.ram': {
+                'help_text':'resource cyclades.ram help text',
+                'is_abbreviation':True,
+                'report_desc':'RAM',
+                'placeholder':'eg. 4GB',
+                'verbose_name':'ram'
+            },
+            'cyclades.cpu': {
+                'help_text':'resource cyclades.cpu help text',
+                'is_abbreviation':True,
+                'report_desc':'CPUs',
+                'placeholder':'eg. 1',
+                'verbose_name':'cpu'
+            },
+            'cyclades.network.private': {
+                'help_text':'resource cyclades.network.private help text',
+                'is_abbreviation':False,
+                'report_desc':'Network',
+                'placeholder':'eg. 1',
+                'verbose_name':'private network'
+            }
+        
+        }
+        
+    })
 
 # Permit local account migration
 ENABLE_LOCAL_ACCOUNT_MIGRATION = getattr(settings, 'ASTAKOS_ENABLE_LOCAL_ACCOUNT_MIGRATION', True)
\ No newline at end of file
index 7c9aa6c..f5db0b5 100644 (file)
@@ -10,7 +10,7 @@ div.cloudbar                                    { background:#000; color:#fff;
 
 div.cloudbar a, 
 div.cloudbar .profile span,
-div.cloudbar li {font-family:'Didact Gothic',Verdana,sans-serif !important; letter-spacing: 1px !important}
+div.cloudbar li                                                                {font-family:'Open Sans',sans-serif !important; letter-spacing: 1px !important}
 
 .cloudbar .wrapper                              { width:auto; padding:0;}                      
 .cloudbar a                                     { color:#fff; text-decoration:none;}
index 241ecdb..2f364e9 100644 (file)
@@ -29,7 +29,7 @@ $(document).ready(function(){
     $("head").append(css);
     
     // load fonts
-    var font_url = 'https://fonts.googleapis.com/css?family=Didact+Gothic&subset=latin,greek,greek-ext';
+    var font_url = 'http://fonts.googleapis.com/css?family=Open+Sans:400,600,700&subset=latin,greek-ext,greek';
     var css_font = $("<link />");
     css_font.attr({rel:'stylesheet', type:'text/css', href:font_url});
     $("head").append(css_font);
index fabb1e1..8deaa6a 100644 (file)
 
 /***** Begin Theme, feel free to edit in here! ******/
 
+
+/* dropkick select extra styles */
+
+.form-row .dk_container                                                        { border-radius:0; margin-bottom:0; border: 1px solid #ccc; height: 21px; letter-spacing: 1px; line-height: 22px; margin-bottom: -1px; width:240px; padding:5px 0; font-weight:normal; font-family: 'Didact Gothic', Verdana, sans-serif; font-size:1em; background:transparent; color:#808080;}
+.form-row .dk_toggle                                                   { border-radius:0;  padding:0;  border:0 none; text-indent:1.5em; text-decoration:none;background-image:url(../images/arrow-down_grey.png); background-position:90% 5px;}
+.form-row .dk_toggle:hover                                             { text-decoration:none; }
+.form-row .dk_open                                                             { background:transparent; box-shadow: none; }
+.form-row .dk_open .dk_toggle                                  { background-color:transparent; border:0 none; color:#000; box-shadow: none;}
+.form-row .dk_focus .dk_toggle                                 { background-color:transparent;  border:0 none; color:#000; box-shadow: none;}
+.1form-row .dk_options                                                 { display:block; }
+.form-row .dk_options                                                  { box-shadow:none; border-radius:0; z-index:3; margin:6px -1px 0; width:auto; left:0;}
+.form-row .dk_options a                                                        { font-weight:normal;color:#808080; padding:5px 0; text-indent:1.5em; border-bottom-color: #ccc }
+.form-row .dk_options a:hover                                  { border-bottom-color: #ccc }
+.form-row .dk_options_inner                                            { padding:0; margin:0; box-shadow:none; text-shadow:none;  border-radius:0; border:1px solid #ccc ; margin-top:4px;}
+.form-row .dk-options_inner li                                 { list-style:none outside; }
+.form-row .dk_options a:hover, 
+.form-row .dk_option_current a                                 { text-shadow:none; background-color: #fff; text-decoration:none; color:#F89A1C}
+
+/* end custom theme */
+
+
+
+
+
 /* One container to bind them... */
 .dk_container {
-   font-family: 'Antic', sans-serif;
-  
-  font-weight: normal;
-  line-height: 42px;
-  letter-spacing: 1px;
-  border: 1px solid #808080;
-  height:42px;
-  display: inline-block;
-  margin-bottom: -1px;
-  padding-left:21px;
-  
-  z-index: 2;
-  width:308px;
+  background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f5f5f5));
+  background: -moz-linear-gradient(top, #fff, #f5f5f5);
+  background: -o-linear-gradient(top, #fff, #f5f5f5);
+  background-color: #f5f5f5;
+  font-family: 'Helvetica', Arial, sans-serif;
+  font-size: 12px;
+  font-weight: bold;
+  line-height: 14px;
+  margin-bottom: 18px;
+  border-radius: 5px;
+  -moz-border-radius: 5px;
+  -webkit-border-radius: 5px;
 }
   .dk_container:focus {
     outline: 0;
@@ -32,8 +55,6 @@
   .dk_container a {
     cursor: pointer;
     text-decoration: none;
-    color:#808080;
-        
   }
 
 /* Opens the dropdown and holds the menu label */
    * Help: Arrow image not appearing
    * Try updating this property to your correct dk_arrows.png path
    */
-   background:url(../images/arrow_02.jpg) no-repeat 276px center;
-  
+  background-image: url('images/dk_arrows.png');
+  background-repeat: no-repeat;
+  background-position: 90% center;
+  border: 1px solid #ccc;
+  color: #333;
+  padding: 7px 45px 7px 10px;
+  text-shadow: #fff 1px 1px 0;
+  border-radius: 5px;
+  -moz-border-radius: 5px;
+  -webkit-border-radius: 5px;
+  -webkit-transition: border-color .5s;
+  -moz-transition: border-color .5s;
+  -o-transition: border-color .5s;
+  transition: border-color .5s;
 }
   .dk_toggle:hover {
-    
+    border-color: #8c8c8c;
   }
   /* Applied when the dropdown is focused */
   .dk_focus .dk_toggle {
-    
+    border-color: #40b5e2;
   }
   .dk_focus .dk_toggle {
-    
+    box-shadow: 0 0 5px #40b5e2;
+    -moz-box-shadow: 0 0 5px #40b5e2;
+    -webkit-box-shadow: 0 0 5px #40b5e2;
   }
 
 /* Applied whenever the dropdown is open */
 .dk_open {
-  
+  box-shadow: 0 0 5px #40b5e2;
+  -moz-box-shadow: 0 0 5px #40b5e2;
+  -webkit-box-shadow: 0 0 5px #40b5e2;
   /**
    * Help: Dropdown menu is covered by something
    * Try setting this value higher
   z-index: 10;
 }
   .dk_open .dk_toggle {
-    
-    
+    background-color: #ececec;
+    border-color: #8c8c8c;
+    color: #ccc;
+    box-shadow: inset 0 -2px 5px #ccc;
+    border-radius: 5px 5px 0 0;
+    -moz-border-radius: 5px 5px 0 0;
+    -webkit-border-radius: 5px 5px 0 0;
   }
 
 /* The outer container of the options */
 .dk_options {
-  
+  box-shadow: rgba(0, 0, 0, .2) 0 2px 8px;
+  -moz-box-shadow: rgba(0, 0, 0, .2) 0 2px 8px;
+  -webkit-box-shadow: rgba(0, 0, 0, .2) 0 2px 8px;
+  border-radius: 0 0 5px 5px;
+  -moz-border-radius: 0 0 5px 5px;
+  -webkit-border-radius: 0 0 5px 5px;
 }
   .dk_options a {
     background-color: #fff;
     border-bottom: 1px solid #999;
-    padding: 8px 20px;
+    font-weight: bold;
+    padding: 8px 10px;
   }
   .dk_options li:last-child a {
     border-bottom: none;
   }
   .dk_options a:hover,
   .dk_option_current a {
-    
+    background-color: #0084c7;
+    border-bottom-color: #004c72;
+    color: #fff;
+    text-decoration: none;
+    text-shadow: rgba(0, 0, 0, .5) 0 1px 0;
   }
 
 /* Inner container for options, this is what makes the scrollbar possible. */
-.dk_options_inner  {
-  padding:0;
-  margin:0;  
-  border: 1px solid #808080;
-  border-top:0 none;
+.dk_options_inner {
+  border: 1px solid #8c8c8e;
+  border-bottom-width: 2px;
+  border-bottom-color: #999;
+  color: #333;
+  max-height: 250px;
+  text-shadow: #fff 0 1px 0;
+  border-radius: 0 0 5px 5px;
+  -moz-border-radius: 0 0 5px 5px;
+  -webkit-border-radius: 0 0 5px 5px;
 }
-.dk_options_inner li   { list-style:none outside;}
 
 /* Set a max-height on the options inner */
 .dk_options_inner,
 
 .dk_container {
   display: none;
+  float: left;
   position: relative;
 }
   .dk_container a {
index 8b80148..e49a712 100644 (file)
@@ -29,7 +29,7 @@ a img, :link img, :visited img                                        { border:none; }
 a span                                                                                 { cursor:pointer; }\r
 abbr[title], dfn[title]                                                        { border-bottom:1px dotted; cursor:help; }\r
 address                                                                                        { font-style:italic; margin:0 0 1.5em 0; }\r
-body                                                                                   { background:white; color:#222; font-family: 'Didact Gothic', Verdana, sans-serif;  font-style:normal; font-size:81.3%; line-height:1.5; text-align:left; letter-spacing:1px;}\r
+body                                                                                   { background:white; color:#222; font-family: 'Open Sans', sans-serif; font-style:normal; font-size:81.3%; line-height:1.5; text-align:left; letter-spacing:1px;}\r
 code, kbd, pre, samp                                                   { font-family:monospace, sans-serif; }\r
 del                                                                                            { text-decoration:line-through; }\r
 dl                                                                                             { margin:1em 0; }\r
index 069b0b0..017fda6 100644 (file)
@@ -5,12 +5,13 @@ input, textarea, .form-widget                                 { background-color: #ffffff; color: #000;borde
 #forms input:focus                                                     { position: relative; border: 1px solid #000; z-index: 100; }\r
 #forms .input:focus label,\r
 #forms input:focus label                                       { z-index: 300; }\r
-form.withlabels label                                          { width: 224px; display: block; float: left; padding-top: 1em; }\r
+form.withlabels label                                          { width: 224px; display: block; float: left; padding-top: 0.8em; }\r
 form.withlabels input[type=text], \r
-form.withlabels input[type=password]           { width: 240px; }\r
+form.withlabels input[type=password]           { width: 270px; }\r
 form.withlabels input[type=text].long, \r
 form.withlabels input[type=password].long, \r
 form.withlabels textarea.long                          { width: 224px; }\r
+form.withlabels textarea                                       { width:270px; }\r
 .login-section a.button                                        { margin-bottom: 12px; }\r
 .login-section a.button:last-child                     { margin-bottom: none;}\r
 .login-section a.button.withicon                       { background-repeat: no-repeat; background-position: 15px 50%; padding-left: 40px; }\r
@@ -33,7 +34,7 @@ form textarea,
 form input.text,\r
 form input[type="text"],\r
 form input[type="password"]                            { color:#808080; font-family: 'Didact Gothic', Verdana, sans-serif; font-weight: normal; line-height: 22px; letter-spacing: 1px;border: 1px solid #808080; height: 21px; display: inline-block; margin-bottom: -1px; padding: 0.8em; padding-left: 1.5em; z-index: 2; width:300px; }\r
-form select                                                                    { font-family: 'Didact Gothic', Verdana, sans-serif; font-weight: normal; line-height: 22px; letter-spacing: 1px;  border: 1px solid #808080; display:block;  margin-bottom: -1px; padding: 0.8em;  padding-left: 1.5em;  z-index: 2;  width:331px; }\r
+form select                                                                    { font-family: 'Didact Gothic', Verdana, sans-serif; font-weight: normal; line-height: 22px; letter-spacing: 1px;  border: 1px solid #808080; display:block;  margin-bottom: -1px; padding: 0.8em;  padding-left: 1.5em;  z-index: 2;  width:301px; }\r
 form textarea:focus,\r
 form input.text:focus,\r
 form input[type="text"]:focus,\r
@@ -44,12 +45,12 @@ form input[type="text"]:focus label,
 form input[type="password"]:focus label        { z-index: 300;}\r
 form input.submit, \r
 form input[type="submit"],\r
-a.submit                                                                       { font-family: 'Didact Gothic', Verdana, sans-serif; font-size: 14px; font-weight: normal; line-height: 22px; letter-spacing:1px;  background-color: #f89a1c;color: #ffffff; border: none; padding: 10px 22px;font-size: 1em; margin:15px 0 0 223px; height:43px; }\r
+a.submit                                                                       { font-family: 'Open Sans', sans-serif; font-size: 14px; font-weight: normal; line-height: 22px; letter-spacing:1px;  background-color: #f89a1c;color: #ffffff; border: none; padding: 10px 22px;font-size: 1em; margin:15px 0 0 223px; height:43px; }\r
 form.innerlabels input.submit, \r
 form.innerlabels input[type="submit"]          {margin-left:0;}\r
 form input.submit:hover, \r
 form input[type="submit"]:hover,\r
-a.submit:hover                                                         { background-color:#3582AC;border-color:#3582ac; text-decoration:none;}\r
+.content a.submit:hover                                                { background-color:#3582AC;border-color:#3582ac; text-decoration:none;}\r
 form input.submit.back, \r
 form input[type="submit"].back                                 { text-decoration: none; bottom: 0; float: right; z-index: 500; }\r
 form input.submit.back.right, \r
@@ -68,15 +69,15 @@ form .with-errors label                                     { color: #e4776f; }
 div.form-stacked                                                       { margin-bottom: 4em; }\r
 .checkbox-widget.checked                                       { background-color: #FF0000; background-image: url("../images/checkbox.png"); background-position: 50% 50%; }\r
 .content a.checkbox-widget                                     { border: 1px solid #808080; cursor: pointer; display: block; float: left; height: 25px; margin:5px 20px 0 0 ; width: 25px; }\r
-form.withlabels .checkbox-widget                       { margin-top:20px; }\r
+form.withlabels .checkbox-widget                       { margin-top:12px; }\r
 form.innerlabels .checkbox-widget +  label     { position:static; line-height:36px; color:#808080; }\r
 form.innerlabels .checkbox-widget +  label + a { border-bottom:1px solid #F89A1C; font-size: 1.1em; }\r
-form span.info                                                         { position:absolute;z-index:10; bottom:32px;  }\r
+form span.info                                                         { position:absolute;z-index:101; top:10px;  }\r
 form.innerlabels span.info                                     { left: 290px; }\r
-form.withlabels span.info                                      { left: 440px; }\r
+form.withlabels span.info                                      { left:485px;  }\r
 form span.info em                                                      { display:block; overflow:hidden;  position:absolute; left:0; text-indent:-100px; top:0; height:21px; width:21px; background:url(../images/symbols.png) no-repeat -4px -31px;cursor:pointer; }\r
 form span.info:hover em                                        { background-position:-4px -3px; }\r
-form span.info span                                            { position:absolute; left:29px; top:-2px; width:120px; padding-left:30px; background:url(../images/black-line.jpg ) no-repeat left 12px; min-height:50px; display:none; font-size:0.846em;}\r
+form span.info span                                            { position:absolute; left:29px; top:-2px; width:120px; padding-left:30px; background:url(../images/black-line.jpg ) no-repeat left 8px; min-height:50px; display:none; font-size:0.846em;}\r
 form span.info:hover span                                      { display:block; }\r
 form .with-errors span.info                                    { display:none;}\r
 form p                                                                         { margin-bottom:0;position:relative;}\r
@@ -87,9 +88,9 @@ form.innerlabels .with-checkbox .checkbox-label               { left:2.5em; top:17px; }
 form.withlabels .with-checkbox .checkbox-widget                { position:absolute; left:224px; top:0; }\r
 \r
 form .extra-img                                                                { display:block; width:21px; height:21px; overflow:hidden;  position:absolute; }\r
-form.withlabels .extra-img                                     { left:460px; bottom:12px; }\r
+form.withlabels .extra-img                                     { left:485px; bottom:12px; }\r
 form.innerlabels .extra-img                                    { bottom:10px; left:290px }\r
-form .with-checkbox .extra-img                         { top:20px; } \r
+form .with-checkbox .extra-img                         {   } \r
 form .with-errors .extra-img                           { background:url(../images/symbols.png) no-repeat -58px -3px;  z-index:101;}\r
 form .with-errors textarea+.extra-img,\r
 form .with-errors noscript+.extra-img          { background:transparent;}\r
@@ -97,6 +98,7 @@ form .with-errors input[type="text"],
 form .with-errors input[type="password"]       { border:1px solid red;}\r
 form.innerlabels .with-errors .extra-img       { left:290px;}\r
 form input[readonly="True"]+ span.extra-img { background:url(../images/symbols.png) no-repeat -111px -3px; z-index:101;}\r
+form.withlabels .with-checkbox .extra-img      { display:none; } \r
 .errorlist                                                                     { margin:0; padding:0;}\r
 .errorlist li                                                          { list-style:none outside;}\r
 \r
@@ -119,24 +121,57 @@ form .with-hidden                                                 { display:none; }
 #okeanos_recaptcha .actions-wrap                               { border-top:1px solid #808080 }\r
 \r
 input.submit:focus,\r
-input[type="submit"]:focus                                             { box-shadow: 0 0 0 1px #FFFFFF inset; border:1px solid #F89A1C; outline:0 none; }                                              \r
+input[type="submit"]:focus,\r
+.content a.submit:focus                                                        { box-shadow: 0 0 0 1px #FFFFFF inset; border:1px solid #F89A1C; outline:0 none; }                                              \r
+.content a.submit:hover                                                        { border-color:#3582AC; }\r
 \r
-@media screen and (max-width : 591px)          { \r
-       form.withlabels .extra-img                                      { left:222px; }\r
-    form.withlabels span.info                                  { left:222px; }\r
-    form .extra-img                                            { left:222px; }\r
+p+ form                                                                                        { margin:2em 0; }\r
+.full-dotted legend                                                            { padding-top:40px; }\r
+form .form-row .radios label                                   { width:auto; float:none; display:inline-block; }\r
+form .form-row .radios input[type="radio"]             { margin-right:165px; vertical-align:middle; }\r
+form.withfieldset textarea                                             { width: 224px; }\r
+form.withfieldset select                                               { width: 256px; background:#fff;}\r
+form+p:first-child, form legend + p                            { margin-bottom:2em; }\r
+\r
+\r
+form.link-like                                                                 { display:inline-block; margin:0 5px; float:right;}\r
+form.link-like input[type="submit"]                            { margin:0; padding:0 5px; background:transparent; color:#F89A1C; cursor:pointer; height:auto; line-height:120%;  }\r
+form.link-like input[type="submit"]:hover              { text-decoration:underline;  }\r
+form.link-like.alone                                                   { float:none; margin:0;}\r
+form.link-like.alone .form-row                                 { margin:0; }\r
+form.link-like.alone input[type="submit"]              { padding:0; }  \r
+.projects form.withlabels .checkbox-widget             { margin-top:5px; }\r
+.projects form .with-checkbox                                  { margin:20px 0; }\r
+.projects form .with-checkbox label                            { padding-top:7px; }\r
+.projects form .with-checkbox span.info                        { bottom:24px; }\r
+.projects .minimal                                                             { float:right; position:relative; margin-bottom:-28px;}\r
+.projects .minimal select                                              { padding:3px; width:250px; }\r
+.projects .minimal label                                               { position:absolute; right:290px; top:5px; white-space:nowrap}\r
+\r
+@media screen and (max-width : 630px)          { \r
+       form.withlabels .extra-img                                      { left:260px; }\r
+    form.withlabels span.info                                  { left:260px; }\r
+    form .extra-img                                            { left:260px; }\r
     .form-error                                                        { margin-left:0!important; } \r
     \r
 }\r
 \r
 \r
 @media screen and (max-width : 410px)          { \r
+       form.withlabels .extra-img                                      { left:90% }    \r
        form.login                                                                      { width:auto; }\r
        form textarea, \r
        form input.text, \r
        form input[type="text"], \r
        form input[type="password"]                             { width:90%; }\r
-       form.withlabels select                                                                  { width:90%; }\r
+       form.withlabels input[type="text"], \r
+    form.withlabels input[type="password"],\r
+    form.withlabels textarea,\r
+    form textarea, \r
+       form input.text, \r
+       form input[type="text"], \r
+       form input[type="password"]                                     { width:90%; }\r
+       form.withlabels select                                          { width:90%; }\r
        form.innerlabels .extra-img,\r
        form.innerlabels .with-errors .extra-img        { left:90%; }\r
        form.innerlabels                                                        { width:auto; }\r
@@ -148,31 +183,14 @@ input[type="submit"]:focus                                                { box-shadow: 0 0 0 1px #FFFFFF inset; border:1p
 }\r
 \r
 \r
-@media screen and ( width : 320px)             { \r
+@media screen and ( max-width : 320px)                 { \r
        #okeanos_recaptcha                                                      { width:259px }\r
-    \r
-}\r
-\r
-p+ form                                                                                        { margin:2em 0; }\r
-.full-dotted legend                                                            { padding-top:40px; }\r
-form .form-row .radios label                                   { width:auto; float:none; display:inline-block; }\r
-form .form-row .radios input[type="radio"]             { margin-right:165px; vertical-align:middle; }\r
-form.withfieldset textarea                                             { width: 224px; }\r
-form.withfieldset select                                               { width: 256px; background:#fff;}\r
-form+p:first-child, form legend + p                            { margin-bottom:2em; }\r
-\r
-/* dropkick select extra styles */\r
-\r
-.form-row .dk_container                                                        { border-radius:0; margin-bottom:0; border: 1px solid #808080; height: 21px; letter-spacing: 1px; line-height: 22px; margin-bottom: -1px; width:254px; padding:0.8em 0; font-weight:normal; font-family: 'Didact Gothic', Verdana, sans-serif; font-size:1em; background:transparent; color:#808080;}\r
-.form-row .dk_toggle                                                   { border-radius:0;  padding:7px 0;  border:0 none; text-indent:1.5em; text-decoration:none;background-image:url(../images/arrow_02.jpg); background-position:90% 5px;}\r
-.form-row .dk_toggle:hover                                             { text-decoration:none; }\r
-.form-row .dk_open                                                             { background:transparent; box-shadow: none; }\r
-.form-row .dk_open .dk_toggle                                  { background-color:transparent; border:0 none; color:#000; box-shadow: none;}\r
-.form-row .dk_focus .dk_toggle                                 { background-color:transparent;  border:0 none; color:#000; box-shadow: none;}\r
-.1form-row .dk_options                                                 { display:block; }
-.form-row .dk_options                                                  { box-shadow:none; border-radius:0; z-index:3; margin:6px -1px 0; width:auto; left:0;}\r
-.form-row .dk_options a                                                        { font-weight:normal;color:#808080 }\r
-.form-row .dk_options_inner                                            { padding:0; margin:0; box-shadow:none; text-shadow:none;  border-radius:0; border:1px solid #8C8C8E ;}\r
-.form-row .dk-options_inner li                                 { list-style:none outside; }\r
-.form-row .dk_options a:hover, \r
-.form-row .dk_option_current a                                 { text-shadow:none; background-color: #E7E7E3;}
\ No newline at end of file
+    form.withlabels input[type="text"], \r
+    form.withlabels input[type="password"],\r
+    form.withlabels textarea,\r
+    form textarea, \r
+       form input.text, \r
+       form input[type="text"], \r
+       form input[type="password"]                                     { width:90%; }\r
+       .form-row .dk_container                                         { width:100%; }\r
+}
\ No newline at end of file
diff --git a/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_flat_0_aaaaaa_40x100.png b/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_flat_0_aaaaaa_40x100.png
new file mode 100644 (file)
index 0000000..5b5dab2
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_flat_0_aaaaaa_40x100.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_55_fbf9ee_1x400.png b/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_55_fbf9ee_1x400.png
new file mode 100644 (file)
index 0000000..ad3d634
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_55_fbf9ee_1x400.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_65_ffffff_1x400.png b/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_65_ffffff_1x400.png
new file mode 100644 (file)
index 0000000..42ccba2
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_65_ffffff_1x400.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_75_dadada_1x400.png b/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_75_dadada_1x400.png
new file mode 100644 (file)
index 0000000..5a46b47
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_75_dadada_1x400.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_75_e6e6e6_1x400.png b/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_75_e6e6e6_1x400.png
new file mode 100644 (file)
index 0000000..86c2baa
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_75_e6e6e6_1x400.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_75_ffffff_1x400.png b/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_75_ffffff_1x400.png
new file mode 100644 (file)
index 0000000..e65ca12
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_glass_75_ffffff_1x400.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png
new file mode 100644 (file)
index 0000000..7c9fa6c
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_inset-soft_95_fef1ec_1x100.png b/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_inset-soft_95_fef1ec_1x100.png
new file mode 100644 (file)
index 0000000..0e05810
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/css/images/ui-bg_inset-soft_95_fef1ec_1x100.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/css/images/ui-icons_222222_256x240.png b/snf-astakos-app/astakos/im/static/im/css/images/ui-icons_222222_256x240.png
new file mode 100644 (file)
index 0000000..b273ff1
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/css/images/ui-icons_222222_256x240.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/css/images/ui-icons_2e83ff_256x240.png b/snf-astakos-app/astakos/im/static/im/css/images/ui-icons_2e83ff_256x240.png
new file mode 100644 (file)
index 0000000..09d1cdc
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/css/images/ui-icons_2e83ff_256x240.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/css/images/ui-icons_454545_256x240.png b/snf-astakos-app/astakos/im/static/im/css/images/ui-icons_454545_256x240.png
new file mode 100644 (file)
index 0000000..59bd45b
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/css/images/ui-icons_454545_256x240.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/css/images/ui-icons_888888_256x240.png b/snf-astakos-app/astakos/im/static/im/css/images/ui-icons_888888_256x240.png
new file mode 100644 (file)
index 0000000..6d02426
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/css/images/ui-icons_888888_256x240.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/css/images/ui-icons_cd0a0a_256x240.png b/snf-astakos-app/astakos/im/static/im/css/images/ui-icons_cd0a0a_256x240.png
new file mode 100644 (file)
index 0000000..2ab019b
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/css/images/ui-icons_cd0a0a_256x240.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/css/jquery-ui-1.8.21.custom.css b/snf-astakos-app/astakos/im/static/im/css/jquery-ui-1.8.21.custom.css
new file mode 100644 (file)
index 0000000..22f42ee
--- /dev/null
@@ -0,0 +1,565 @@
+/*!
+ * jQuery UI CSS Framework 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; }
+.ui-helper-clearfix:after { clear: both; }
+.ui-helper-clearfix { zoom: 1; }
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+
+/*!
+ * jQuery UI CSS Framework 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ *
+ * To view and modify this theme, visit http://jqueryui.com/themeroller/?ctl=themeroller
+ */
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; }
+.ui-widget .ui-widget { font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_glass_75_ffffff_1x400.png) 50% 50% repeat-x; color: #222222; }
+.ui-widget-content a { color: #222222; }
+.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold; }
+.ui-widget-header a { color: #222222; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #555555; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999; background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #212121; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121; text-decoration: none; }
+.ui-widget :active { outline: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight  {border: 1px solid #fcefa1; background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_inset-soft_95_fef1ec_1x100.png) 50% bottom repeat-x; color: #cd0a0a; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary,  .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -khtml-border-top-left-radius: 4px; border-top-left-radius: 4px; }
+.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -khtml-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -khtml-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; -khtml-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); }
+.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -khtml-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/*!
+ * jQuery UI Resizable 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Resizable#theming
+ */
+.ui-resizable { position: relative;}
+.ui-resizable-handle { position: absolute;font-size: 0.1px; display: block; }
+.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
+.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
+.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
+.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
+.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
+.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
+.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
+.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
+.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/*!
+ * jQuery UI Selectable 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Selectable#theming
+ */
+.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; }
+/*!
+ * jQuery UI Accordion 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Accordion#theming
+ */
+/* IE/Win - Fix animation bug - #4615 */
+.ui-accordion { width: 100%; }
+.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
+.ui-accordion .ui-accordion-li-fix { display: inline; }
+.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
+.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; }
+.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; }
+.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
+.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; }
+.ui-accordion .ui-accordion-content-active { display: block; }
+/*!
+ * jQuery UI Autocomplete 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Autocomplete#theming
+ */
+.ui-autocomplete { position: absolute; cursor: default; }      
+
+/* workarounds */
+* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
+
+/*
+ * jQuery UI Menu 1.8.21
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Menu#theming
+ */
+.ui-menu {
+       list-style:none;
+       padding: 2px;
+       margin: 0;
+       display:block;
+       float: left;
+}
+.ui-menu .ui-menu {
+       margin-top: -3px;
+}
+.ui-menu .ui-menu-item {
+       margin:0;
+       padding: 0;
+       zoom: 1;
+       float: left;
+       clear: left;
+       width: 100%;
+}
+.ui-menu .ui-menu-item a {
+       text-decoration:none;
+       display:block;
+       padding:.2em .4em;
+       line-height:1.5;
+       zoom:1;
+}
+.ui-menu .ui-menu-item a.ui-state-hover,
+.ui-menu .ui-menu-item a.ui-state-active {
+       font-weight: normal;
+       margin: -1px;
+}
+/*!
+ * jQuery UI Button 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Button#theming
+ */
+.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
+.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
+button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
+.ui-button-icons-only { width: 3.4em; } 
+button.ui-button-icons-only { width: 3.7em; } 
+
+/*button text element */
+.ui-button .ui-button-text { display: block; line-height: 1.4;  }
+.ui-button-text-only .ui-button-text { padding: .4em 1em; }
+.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
+.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; }
+.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; }
+.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
+/* no icon support for input elements, provide padding by default */
+input.ui-button { padding: .4em 1em; }
+
+/*button icon element(s) */
+.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
+.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
+.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; }
+.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+
+/*button sets*/
+.ui-buttonset { margin-right: 7px; }
+.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
+
+/* workarounds */
+button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
+/*!
+ * jQuery UI Dialog 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Dialog#theming
+ */
+.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; }
+.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative;  }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; } 
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; }
+.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
+/*!
+ * jQuery UI Slider 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Slider#theming
+ */
+.ui-slider { position: relative; text-align: left; }
+.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
+.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
+
+.ui-slider-horizontal { height: .8em; }
+.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
+.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
+.ui-slider-horizontal .ui-slider-range-min { left: 0; }
+.ui-slider-horizontal .ui-slider-range-max { right: 0; }
+
+.ui-slider-vertical { width: .8em; height: 100px; }
+.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
+.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
+.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
+.ui-slider-vertical .ui-slider-range-max { top: 0; }/*!
+ * jQuery UI Tabs 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Tabs#theming
+ */
+.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
+.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; }
+.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; }
+.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
+.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
+.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; }
+.ui-tabs .ui-tabs-hide { display: none !important; }
+/*!
+ * jQuery UI Datepicker 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Datepicker#theming
+ */
+.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; }
+.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
+.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
+.ui-datepicker .ui-datepicker-prev { left:2px; }
+.ui-datepicker .ui-datepicker-next { right:2px; }
+.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
+.ui-datepicker .ui-datepicker-next-hover { right:1px; }
+.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px;  }
+.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
+.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
+.ui-datepicker select.ui-datepicker-month, 
+.ui-datepicker select.ui-datepicker-year { width: 49%;}
+.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
+.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0;  }
+.ui-datepicker td { border: 0; padding: 1px; }
+.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
+.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
+.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi { width:auto; }
+.ui-datepicker-multi .ui-datepicker-group { float:left; }
+.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
+.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
+.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
+.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
+.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; }
+
+/* RTL support */
+.ui-datepicker-rtl { direction: rtl; }
+.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+
+/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
+.ui-datepicker-cover {
+    display: none; /*sorry for IE5*/
+    display/**/: block; /*sorry for IE5*/
+    position: absolute; /*must have*/
+    z-index: -1; /*must have*/
+    filter: mask(); /*must have*/
+    top: -4px; /*must have*/
+    left: -4px; /*must have*/
+    width: 200px; /*must have*/
+    height: 200px; /*must have*/
+}/*!
+ * jQuery UI Progressbar 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Progressbar#theming
+ */
+.ui-progressbar { height:2em; text-align: left; overflow: hidden; }
+.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }
\ No newline at end of file
index ad953d6..c303af6 100644 (file)
@@ -22,13 +22,13 @@ img.right                                                           { margin:0 0 1em 1em; float:right;}
 \r
 \r
 /*top message*/\r
-.top-msg                                           { margin:-36px -70px 36px; background:blue; padding:100px; color:#fff; display:none; position:relative; font-size:1.538em; text-align:center;}\r
+.top-msg                                           { margin:-36px -70px 36px; background:blue; padding:50px; color:#fff; display:none; position:relative; font-size:1.538em; text-align:center;}\r
 .top-msg p                                                                             { text-align:center; }\r
 .top-msg p.title                                   { font-size:1.3em;  }\r
 .top-msg p.title span                          { border-bottom:2px dotted #fff; padding:0 0 10px 0;}\r
 .top-msg.active                                { display:block;}\r
 .top-msg +.mainlogo                             { margin-top:-73px;}\r
-.top-msg .close                                 { position:absolute; bottom:20px; right:20px; font-size:1.3em; font-weight:bold; border:0 none; color:#fff; text-decoration:none;}\r
+.top-msg .close                                 { position:absolute; bottom:20px; right:20px; font-size:1.3em;  border:0 none; color:#fff; text-decoration:none;}\r
 .top-msg .close:hover                           { color:#000;}\r
 .top-msg.success                               { background-color:#77C596; color: #fff}\r
 .top-msg.error                                 { background-color:#EF4F54; color: #fff}\r
@@ -55,7 +55,8 @@ img.right                                                             { margin:0 0 1em 1em; float:right;}
 .two-cols .lt                                   { float:left; width:400px;}\r
 .two-cols-blog .rt                              { float:right; width:220px; margin-left:80px; padding-right:65px;}\r
 .two-cols-blog .lt                                 { overflow:hidden;}\r
-.container h2, .container h3                    { font-weight:normal; margin-bottom:1em; }\r
+.container h2                                                                  { font-weight:normal; font-size:1.308em; margin-bottom:1em; }\r
+.container h3                                                  { font-weight:normal; margin-bottom:1em; }\r
 .container h2 em                                                               { color: #3582AC; font-style:normal; }\r
 /*.content a                                                           { border-bottom: 1px solid #F89A1C; text-decoration:none; color:#000; }\r
 .content a:hover                                                   { border-bottom: 1px solid #F89A1C; text-decoration:none; color:#F89A1C; }*/\r
@@ -77,8 +78,8 @@ img.right                                                             { margin:0 0 1em 1em; float:right;}
 .content .backlink                                                             { margin:1em 0; }\r
 .faq h3                                                                                        { color:#4085a6; margin:0;}\r
 .faq ul                                         { padding:0; margin:0; }\r
-.faq ul li                                      { list-style:none outside; padding:0; margin:0;  }\r
-.faq ul li a                                    { color:#222222; border:0 none;} \r
+.faq ul li                                      { padding:0; margin:0 0 8px 0; list-style:none outside none;line-height:140%;  }\r
+.faq ul li a                                    { color:#222222; border:0 none; } \r
 .faq .faq-category                                                             { margin: 0 0 1.5em;}\r
 .follow h3 a                                    { color:#4085A6;}\r
 .follow a                                                      { border:0 none;}\r
@@ -223,12 +224,14 @@ a.button:hover                                                                    { background-color: #F89A1C; border:0 none; color:#fff;}
 \r
 /* Style for im/projects */\r
 table.alt-style                                                                { color:#000; width:100%; }\r
-table.alt-style caption                                                        { font-weight:normal;  font-size:1.154em; }\r
+table.alt-style caption                                                        { font-weight:normal;  font-size:1.154em; margin-bottom:15px;}\r
 table.alt-style tr th                                                  { font-weight:normal; color:#3582AC }\r
 table.alt-style tr td                                                  { color:#222; }\r
 table.alt-style tr td:first-child,\r
 table.alt-style tr th:first-child                              { padding-left:5px; }\r
-.content a.submit                                                              { margin:0; display:inline-block; margin:10px 0 ;  height:auto;}\r
+table.alt-style tr td a                                                        { margin:0 0 0 20px; }\r
+table.alt-style tr td:first-child a                            { margin:0; }\r
+.content a.submit                                                              { margin:0; display:inline-block; margin:10px 0 ;  height:auto; min-width:100px; text-align:center;}\r
 table.alt-style tr:nth-child(2n) td                            { background:#F2F2F2 }\r
 dl.alt-style                                                                   { width:500px; }\r
 dl.alt-style dt                                                                        { width:50%; float:left; color:#3582AC; font-weight:normal;}\r
@@ -276,14 +279,192 @@ dl.alt-style dt:nth-child(2n)                                    { background:black; }
 \r
 .question .content a:hover                                             { color:#F6921E }\r
 .details .extra-menu                                                   { background: url(../images/dots.jpg) repeat-x  center top ;padding-top:20px; margin-top:20px; }\r
+.details .extra-menu h3                                                        { margin-bottom:1em; font-weight:bold;}\r
 .details .faq-category h2                                              { font-size:1em; }\r
 .question .next-prev                                                   { margin:10px 0; }\r
 .question .next                                                                        { float:right; }\r
 .details img                                                                   { max-width:100%; }\r
 .question .section                                                             { margin-top:1em; }\r
 .question pre                                                                  { border:1px dashed #000; padding:5px; margin:10px 0; line-height:auto; }\r
-.widjets                                                                               { margin: 0; padding:0; }\r
+/*\r
+.widjets                                                                               { position:relative; }\r
+.widjets ul                                                                            { margin: 0; padding:0; }\r
 .widjets li                                                                            { width:50%; float:left; list-style:none outside; margin:30px 0; }\r
 .widjets li div                                                                        { border:1px dashed #000; padding:20px 20px 70px; width:60%; margin:0 auto; position:relative; }\r
 .widjets li div img                                                            { max-width:100%; }\r
 .widjets li .btn                                                               { text-align:center; position:absolute; bottom:0; left:0; right:0; }\r
+.widjets .widjet-x                                                             { position:absolute; right:0;top:0; font-weight:bold; font-size:1.5em; }\r
+.widjets .widjet-x:hover                                               { text-decoration:none; color:#000; }\r
+\r
+ */\r
+.widjets                                                                               { position:relative; }\r
+.widjets ul                                                                            { margin: 0; padding:0; }\r
+.widjets li                                                                            { width:50%; float:left; list-style:none outside; margin:30px 0;  }\r
+.widjets li div                                                                        { background:url(../images/dots.jpg) repeat-x top ; margin:0 0 0 20px; position:relative;   padding:2em 0; }\r
+.widjets li div .wrap                                                  { background:url(../images/dots.jpg) repeat-x bottom ; }\r
+.widjets li:first-child div                                    { margin-right:20px; margin-left:0; }\r
+.widjets li div img                                                            { max-width:100%; margin:2em 0; }\r
+.widjets li div .txt                                                   { height:100px; }\r
+.widjets .widjet-x                                                             { position:absolute; right:0;top:0; font-weight:bold; font-size:1.5em; }\r
+.widjets .widjet-x:hover                                               { text-decoration:none; color:#000; }\r
+.widjets li.create h2, \r
+.widjets li.create a                                                   { color: #55B577}\r
+.widjets li.join h2, \r
+.widjets li.join a                                                             { color: #F24E53}\r
+.widjets li a                                                                  { font-size:1.154em; }\r
+\r
+\r
+/* billing styles */\r
+.alt-style .table-div                                                  { border:1px dashed #000; }\r
+.billing table.complex tr:nth-child(2n) td             { background:transparent; }\r
+.billing table.alt-style tr.zebra td                   { background:#F2F2F2; }\r
+.billing .highlight                                                            { text-align:center; padding:10px; border:1px dashed #000; font-size:1.231em; margin:0 0 2em; position:relative;}\r
+.billing .highlight em                                                 { color:#3582AC; font-style:normal; font-weight:bold; }\r
+.billing table.marginless                                              { margin-bottom:0; }\r
+.billing .sum                                                                  { background:#5A97B8; padding:5px 5px 5px 10px; color:#fff; }\r
+.billing.details .last                                                 { width:15%;}\r
+.billing.details .prelast                                              { width:10%;}\r
+.billing.list .last                                                            { width:20%; }\r
+.billing.list .prelast                                                 { width:20%; }\r
+.billing form                                                                  { margin-top:50px; }\r
+.billing .categories                                                   { margin-bottom:2em; }\r
+.billing .categories .clear                    { color: #000000; position:relative; top:-1px; line-height:100%; display:inline-block;}\r
+.billing  a, .billing  a:hover                 { border:0 none;}\r
+.billing .categories ul                            { margin:0;padding:0;}\r
+.billing .categories ul li                     { float: left; list-style:none outside;}\r
+.billing .categories .title                    { margin-bottom: 0.5em; }\r
+.billing .categories ul li a                   { color: #000000;   margin-right: 22px; text-decoration: none; }\r
+.billing .categories ul li a:hover, \r
+.billing .categories ul li a.selected          { color: #000000;}\r
+.billing .categories ul li.active a                            { text-decoration: underline }\r
+.billing .categories ul li.inactive            { opacity: 0.3; }\r
+.billing .categories ul li.active              { opacity: 1; }\r
+.billing .resource-cat-1.filter-item a,\r
+.billing .resource-cat-1.filter-item a:hover   { color:#ff6f00 }\r
+.billing .resource-cat-2.filter-item a,\r
+.billing .resource-cat-2.filter-item a:hover   { color:#4085A5 }\r
+.billing span.info                                                             { position:absolute;z-index:10; bottom:32px; right:40px; }\r
+.billing span.info em                                                  { display:block; overflow:hidden;  position:absolute; left:0; text-indent:-110px; top:0; height:21px; width:21px; background:url(../images/symbols.png) no-repeat -4px -31px;cursor:pointer; }\r
+.billing span.info:hover em                                    { background-position:-4px -3px; }\r
+.billing span.info span                                                { position:absolute; left:29px; top:-2px; width:120px; padding-left:30px; background:url(../images/black-line.jpg ) no-repeat left 12px ; min-height:50px; display:none; font-size:12px;}\r
+.billing span.info:hover span                                  { display:block; }\r
+.billing span.info.foo span                                            { padding:0; background:transparent; left:-48px; top:22px; font-size:12px;}\r
+.billing .highlight a                                                  { float:right; }\r
+.billing .highlight .popup                                             { position:absolute;right:20px; top:40px; width: 100px;  border:1px solid #000; }\r
+.billing table.alt-style tr td                                 { border-top:10px solid #fff; }\r
+.billing table.alt-style .last                                 { text-align:right; }\r
+\r
+.table_sorting tr th                                                   { cursor:pointer; }\r
+\r
+.table_sorting tr th:hover                                             { text-decoration:underline }\r
+\r
+table.alt-style tr.tr1 td,\r
+table.alt-style tr.tmore1 td                                   { background:#F2F2F2 }\r
+table.alt-style tr.tr2 td,\r
+table.alt-style tr.tmore2 td                                   { background:#fff }\r
+table.alt-style tr td.info-td                                  { padding:5px; }\r
+table.alt-style tr td.info-td div                              { padding:15px; border:1px dashed #000 }\r
\r
+\r
+.projects .details a.edit                                              { float:right; margin-left:20px;  }\r
+.projects .details .data                                               { overflow:hidden; }\r
+.projects .editable form textarea                              { width:70%; height:50px; max-width:70%; width:270px; height:120px;}\r
+\r
+\r
+/* quotas-form  */\r
+\r
+.quotas-form fieldset                                                  { background:url(../images/dots.jpg) repeat-x scroll center bottom transparent; margin-bottom:3em; padding-bottom:3em; position:relative; }\r
+.quotas-form legend                                                            { color:#55B577; font-size:1.308em; margin-bottom:3em; position:relative; }\r
+.quotas-form legend span                                               { color:#222; font-size:0.867em; }\r
+form.quotas-form legend span.info                              { position:relative; display:inline-block; top:auto; left:auto;  margin-left:10px; vertical-align:middle; margin-top:-2px;}\r
+form.quotas-form legend span.info em                   { position:static; }\r
+form.quotas-form legend span.info span                 { width:395px; }\r
+.quotas-form .with-checkbox .checkbox-widget   { margin-top:9px; } \r
+.quotas-form .with-checkbox span.info                  { top:12px; }\r
+.quotas-form .form-row.submit                                  { text-align:center; }\r
+.quotas-form input[type="submit"]                              { margin:15px 0; background-color:#B3B3B3 }\r
+.quotas-form input[type="submit"]:hover                        { background:#55B577 }\r
+.quotas-form input[type="submit"]:focus                        { border-color: #B3B3B3}\r
+.quotas-form input[type="submit"]:focus:hover  { border-color: #55B577}\r
+.quotas-form fieldset ul                                               { padding:0; margin:0 0 1em; position:relative; }\r
+.quotas-form fieldset ul li                                            { list-style:none outside none; float:left; padding:0 0 0 60px; margin:0; }\r
+.quotas-form fieldset ul li:first-child                        { padding-left:0; }\r
+.quotas-form fieldset ul li a                                  { display:block; width:82px; height:82px; overflow:hidden; }\r
+.quotas-form fieldset ul li a:hover    img                     { margin-top:-84px; }\r
+.quotas-form fieldset ul li a.selected img             { margin-top:-168px; }\r
+.quotas-form fieldset ul li a.selected:hover   { cursor:default }\r
+.quotas-form fieldset ul li a.selected:focus   { outline:0 none; }\r
+.quotas-form fieldset ul li p                                  { position:absolute; top:95px; left:0; display: none;}\r
+.quotas-form fieldset ul li:hover p                            { display:block; }\r
+.quotas-form p.msg                                                             { color:#B3B3B3; }\r
+.quotas-form a.delete                                                  { position:absolute; right:0; top:0; color:#B3B3B3; z-index:2 }\r
+.quotas-form .group                                                            { display:none; position:relative; background:url(../images/dots.jpg) repeat-x scroll center bottom; margin-bottom:2em; padding-bottom:2em;}\r
+.quotas-form .group fieldset                                   { background:transparent; margin-bottom:1em; padding-bottom:1em; }\r
+.quotas-form .group fieldset legend                            { margin-bottom:1em; padding-bottom:1em; }\r
+.quotas-form fieldset ul li.rel+li.rel                 { background:url(../images/quota-related-bg.png) no-repeat left center; }\r
+.quotas-form .double-checks label                              { font-size:1.077em; }\r
+.quotas-form .double-checks .form-row                  { float:left; margin-right:10px;}\r
+.quotas-form .double-checks .with-checkbox .checkbox-widget    { left:0; }\r
+.quotas-form .double-checks .with-checkbox input[type="text"]  { width:60px; float:left; margin:9px 15px -9px; display:none; padding:6px; }\r
+.quotas-form .double-checks .with-checkbox label{ width:auto; float:left; margin-left:35px; }\r
+.quotas-form .double-checks .with-checkbox input[type="text"].hideshow { display:block; }\r
+.quotas-form .with-checkbox+.with-checkbox             { width:196px; }\r
+.summary dl.alt-style dt                                               { color:#55B577; }\r
+.quotas-form .with-info .double-checks p               { clear:both; }\r
+.quotas-form .with-info .with-checkbox+.with-checkbox          { width:auto; }\r
+.quotas-form .with-info .double-checks                         { position:relative; margin-bottom:70px; }\r
+.quotas-form .with-info .double-checks .form-row+.form-row                                     { position:absolute; left:224px; top:40px;}\r
+.quotas-form .with-info .double-checks span.info { left:262px; }\r
+.quotas-form .with-info .with-checkbox                 { margin-bottom:12px; }  
+.quotas-form .quota input[type="text"]                 { width:150px;}\r
+.quotas-form .quota .error-msg                                         { display:none; color:red; font-size:0.8em; margin:0; margin-left:224px; margin-bottom:5px; padding:5px; }\r
+.quotas-form .quota .with-errors .error-msg            { display:block;}        \r
+::-webkit-input-placeholder                                    { color: #D4D4D4; font-style:italic; }\r
+:-moz-placeholder                                                              { color: #D4D4D4;font-style:italic; }\r
+\r
+/* stats */\r
+.stats ul                                                                              { margin:0; padding:0; list-style:none outside none; }\r
+.stats ul li                                                                   { margin:0 0 1em 0; padding:0 0 1em 0; list-style:none outside none; background:url(../images/stats-line.jpg) repeat-x left bottom}\r
+.stats .bar                                                                            { padding: 0; text-align:center;  float:left; width:200px;}\r
+.stats .bar div                                                                        { width:340px; height:30px; border:1px solid #000; margin-top:20px; overflow:hidden;}\r
+.stats .bar span                                                               { text-align:right; display:block; height:100%; color:#222;  line-height:30px; font-size:1.231em; text-indent:10px;}\r
+.stats .red .bar span                                                  { background:#ef4f54; }\r
+.stats .yellow .bar span                                               { background:#f6921e; }\r
+.stats .green .bar span                                                        { background:#55b577; }\r
+.stats .img-wrap                                                               { float:left; width:100px; background:url(../images/statistics_icons.png) no-repeat center center; padding:30px 0; }\r
+.stats .info                                                                   { margin:0 25px ; width:320px; float:left;  }\r
+.stats .info p                                                                 { color:#999; margin:0; }\r
+.stats .info h3                                                                        { font-size:1.231em; color:#222222 }\r
+.stats .vm .img-wrap                                                   { background-image:url(../images/vm-stats.png) }\r
+.stats .ram .img-wrap                                                  { background-image:url(../images/ram-stats.png) }\r
+.stats .cpu .img-wrap                                                  { background-image:url(../images/cpu-stats.png) }\r
+.stats .network .img-wrap                                              { background-image:url(../images/network-stats.png) }\r
+.stats .disksize .img-wrap                                             { background-image:url(../images/disk-stats.png) }\r
+.stats .disk .img-wrap                                                 { background-image:url(../images/disk-stats.png) }\r
+.stats .diskspace .img-wrap                                            { background-image:url(../images/storage-stats.png) }\r
+.stats .bandwidth .img-wrap                                            { background-image:url(../images/bandwidth-stats.png) }\r
+\r
+.stats .red .img-wrap                                                  { background-position: 15px 7px; }\r
+.stats .yellow .img-wrap                                               { background-position: -124px 7px; }\r
+.stats .green .img-wrap                                                        { background-position: -263px 7px; }\r
+.projects .editable form textarea                              { width:70%; height:50px; max-width:70%; width:270px; height:120px;}\r
+\r
+/* temp style to hide extra menu for groups */\r
+.navigation ul+ul                                                              { display:none; }\r
+\r
+table .msg-wrap                                                                        { position:relative; }\r
+table .msg-wrap .dialog                                                        { position:absolute; border:1px dashed #ccc;  padding:15px; width:200px; bottom:30px; left:0; background:#fff; display:none; }\r
+table .msg-wrap .dialog .submit                                        { min-width:30px; padding:5px 22px; }\r
+table .msg-wrap .dialog .no.submit                             { float:right; }\r
+table.alt-style .centered                                              { text-align:center; }\r
+table.alt-style form.link-like                                 { float:none }\r
+form.quotas-form span.error-msg span                   { display:block; color:red; }\r
+form.quotas-form span.error-msg em,\r
+form.quotas-form span.error-msg:hover em               { background-position:-58px -3px; }\r
+.two-cols-links                                                                        { margin:5em 0; }\r
+.two-cols-links p                                                              { width:auto; overflow:hidden; }\r
+.two-cols-links a                                                              { color:grey; display:block; margin-bottom:10px;}\r
+.two-cols-links a:hover                                                        { color:#F89A1C }\r
+.two-cols-links p:first-child                                  { width:224px; float:left; overflow:auto }\r
+.two-cols-links p:first-child a                                        { color:#222; }\r
+.two-cols-links p:first-child a:hover                  { color:#F89A1C }\r
diff --git a/snf-astakos-app/astakos/im/static/im/css/uniform.default.css b/snf-astakos-app/astakos/im/static/im/css/uniform.default.css
new file mode 100644 (file)
index 0000000..3e7e485
--- /dev/null
@@ -0,0 +1,616 @@
+/*
+
+Uniform Theme: Uniform Default
+Version: 1.6
+By: Josh Pyles
+License: MIT License
+---
+For use with the Uniform plugin:
+http://pixelmatrixdesign.com/uniform/
+---
+Generated by Uniform Theme Generator:
+http://pixelmatrixdesign.com/uniform/themer.html
+
+*/
+
+/* Global Declaration */
+
+div.selector, 
+div.selector span, 
+div.checker span,
+div.radio span, 
+div.uploader, 
+div.uploader span.action,
+div.button,
+div.button span {
+  background-image: url(../images/sprite.png);
+  background-repeat: no-repeat;
+  -webkit-font-smoothing: antialiased;
+}
+
+.selector, 
+.radio, 
+.checker, 
+.uploader,
+.button, 
+.selector *, 
+.radio *, 
+.checker *, 
+.uploader *,
+.button *{
+  margin: 0;
+  padding: 0;
+}
+
+/* INPUT & TEXTAREA */
+
+input.text,
+input.email, 
+input.password,
+textarea.uniform {
+  font-size: 12px;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-weight: normal;
+  padding: 3px;
+  color: #777;
+  background: url('../images/bg-input-focus.png') repeat-x 0px 0px;
+  background: url('../images/bg-input.png') repeat-x 0px 0px;
+  border-top: solid 1px #aaa;
+  border-left: solid 1px #aaa;
+  border-bottom: solid 1px #ccc;
+  border-right: solid 1px #ccc;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+  outline: 0;
+}
+
+input.text:focus,
+input.email:focus,
+input.password:focus,
+textarea.uniform:focus {
+  -webkit-box-shadow: 0px 0px 4px rgba(0,0,0,0.3);
+  -moz-box-shadow: 0px 0px 4px rgba(0,0,0,0.3);
+  box-shadow: 0px 0px 4px rgba(0,0,0,0.3);
+  border-color: #999;
+  background: url('../images/bg-input-focus.png') repeat-x 0px 0px;
+}
+
+/* SPRITES */
+
+/* Select */
+
+div.selector {
+  background-position: -483px -130px;
+  line-height: 26px;
+  height: 26px;
+}
+
+div.selector span {
+  background-position: right 0px;
+  height: 26px;
+  line-height: 26px;
+}
+
+div.selector select {
+  /* change these to adjust positioning of select element */
+  top: 0px;
+  left: 0px;
+}
+
+div.selector:active, 
+div.selector.active {
+  background-position: -483px -156px;
+}
+
+div.selector:active span, 
+div.selector.active span {
+  background-position: right -26px;
+}
+
+div.selector.focus, div.selector.hover, div.selector:hover {
+  background-position: -483px -182px;
+}
+
+div.selector.focus span, div.selector.hover span, div.selector:hover span {
+  background-position: right -52px;
+}
+
+div.selector.focus:active,
+div.selector.focus.active,
+div.selector:hover:active,
+div.selector.active:hover {
+  background-position: -483px -208px;
+}
+
+div.selector.focus:active span,
+div.selector:hover:active span,
+div.selector.active:hover span,
+div.selector.focus.active span {
+  background-position: right -78px;
+}
+
+div.selector.disabled {
+  background-position: -483px -234px;
+}
+
+div.selector.disabled span {
+  background-position: right -104px;
+}
+
+/* Checkbox */
+
+div.checker {
+  width: 19px;
+  height: 19px;
+}
+
+div.checker input {
+  width: 19px;
+  height: 19px;
+}
+
+div.checker span {
+  background-position: 0px -260px;
+  height: 19px;
+  width: 19px;
+}
+
+div.checker:active span, 
+div.checker.active span {
+  background-position: -19px -260px;
+}
+
+div.checker.focus span,
+div.checker:hover span {
+  background-position: -38px -260px;
+}
+
+div.checker.focus:active span,
+div.checker:active:hover span,
+div.checker.active:hover span,
+div.checker.focus.active span {
+  background-position: -57px -260px;
+}
+
+div.checker span.checked {
+  background-position: -76px -260px;
+}
+
+div.checker:active span.checked, 
+div.checker.active span.checked {
+  background-position: -95px -260px;
+}
+
+div.checker.focus span.checked,
+div.checker:hover span.checked {
+  background-position: -114px -260px;
+}
+
+div.checker.focus:active span.checked,
+div.checker:hover:active span.checked,
+div.checker.active:hover span.checked,
+div.checker.active.focus span.checked {
+  background-position: -133px -260px;
+}
+
+div.checker.disabled span,
+div.checker.disabled:active span,
+div.checker.disabled.active span {
+  background-position: -152px -260px;
+}
+
+div.checker.disabled span.checked,
+div.checker.disabled:active span.checked,
+div.checker.disabled.active span.checked {
+  background-position: -171px -260px;
+}
+
+/* Radio */
+
+div.radio {
+  width: 25px;
+  height: 25px;
+  
+}
+
+div.radio input {
+  width: 25px;
+  height: 25px;
+}
+
+div.radio span {
+  height: 25px;
+  width: 25px;
+  background:transparent;
+}
+
+div.radio span span {
+        border:1px solid grey;
+}
+div.radio:active span, 
+div.radio.active span {
+  background-image: url(../images/checkbox.png);
+  background-position:top left;
+}
+
+div.radio.focus span, 
+div.radio:hover span {
+  background:transparent;
+}
+
+div.radio.focus:active span,
+div.radio:active:hover span,
+div.radio.active:hover span,
+div.radio.active.focus span {
+  background-image: url(../images/checkbox.png);
+}
+
+div.radio span.checked {
+ background-image: url(../images/checkbox.png);
+}
+
+div.radio:active span.checked,
+div.radio.active span.checked {
+  background-image: url(../images/checkbox.png);
+}
+
+div.radio.focus span.checked, div.radio:hover span.checked {
+  background-image: url(../images/checkbox.png);
+}
+
+div.radio.focus:active span.checked, 
+div.radio:hover:active span.checked,
+div.radio.focus.active span.checked,
+div.radio.active:hover span.checked {
+  background-image: url(../images/checkbox.png);
+}
+
+div.radio.disabled span,
+div.radio.disabled:active span,
+div.radio.disabled.active span {
+  background-position: -144px -279px;
+}
+
+div.radio.disabled span.checked,
+div.radio.disabled:active span.checked,
+div.radio.disabled.active span.checked {
+  background-position: -162px -279px;
+}
+
+/* Uploader */
+
+div.uploader {
+  background-position: 0px -297px;
+  height: 28px;
+}
+
+div.uploader span.action {
+  background-position: right -409px;
+  height: 24px;
+  line-height: 24px;
+}
+
+div.uploader span.filename {
+  height: 24px;
+  /* change this line to adjust positioning of filename area */
+  margin: 2px 0px 2px 2px;
+  line-height: 24px;
+}
+
+div.uploader.focus,
+div.uploader.hover,
+div.uploader:hover {
+  background-position: 0px -353px;
+}
+
+div.uploader.focus span.action,
+div.uploader.hover span.action,
+div.uploader:hover span.action {
+  background-position: right -437px;
+}
+
+div.uploader.active span.action,
+div.uploader:active span.action {
+  background-position: right -465px;
+}
+
+div.uploader.focus.active span.action,
+div.uploader:focus.active span.action,
+div.uploader.focus:active span.action,
+div.uploader:focus:active span.action {
+  background-position: right -493px;
+}
+
+div.uploader.disabled {
+  background-position: 0px -325px;
+}
+
+div.uploader.disabled span.action {
+  background-position: right -381px;
+}
+
+div.button {
+  background-position: 0px -523px;
+}
+
+div.button span {
+  background-position: right -643px;
+}
+
+div.button.focus,
+div.button:focus,
+div.button:hover,
+div.button.hover {
+  background-position: 0px -553px;
+}
+
+div.button.focus span,
+div.button:focus span,
+div.button:hover span,
+div.button.hover span {
+  background-position: right -673px; 
+}
+
+div.button.active,
+div.button:active {
+  background-position: 0px -583px;
+}
+
+div.button.active span,
+div.button:active span {
+  background-position: right -703px;
+  color: #555;
+}
+
+div.button.disabled,
+div.button:disabled {
+  background-position: 0px -613px;
+}
+
+div.button.disabled span,
+div.button:disabled span {
+  background-position: right -733px;
+  color: #bbb;
+  cursor: default;
+}
+
+/* PRESENTATION */
+
+/* Button */
+
+div.button {
+  height: 30px;
+}
+
+div.button span {
+  margin-left: 13px;
+  height: 22px;
+  padding-top: 8px;
+  font-weight: bold;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 12px;
+  letter-spacing: 1px;
+  text-transform: uppercase;
+  padding-left: 2px;
+  padding-right: 15px;
+}
+
+/* Select */
+div.selector {
+  width: 190px;
+  font-size: 12px;
+}
+
+div.selector select {
+  min-width: 190px;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 12px;
+  border: solid 1px #fff;
+}
+
+div.selector span {
+  padding: 0px 25px 0px 2px;
+  cursor: pointer;
+}
+
+div.selector span {
+  color: #666;
+  width: 158px;
+  text-shadow: 0 1px 0 #fff;
+}
+
+div.selector.disabled span {
+  color: #bbb;
+}
+
+/* Checker */
+div.checker {
+  margin-right: 5px;
+}
+
+/* Radio */
+div.radio {
+  margin-right: 3px;
+}
+
+/* Uploader */
+div.uploader {
+  width: 190px;
+  cursor: pointer;
+}
+
+div.uploader span.action {
+  width: 85px;
+  text-align: center;
+  text-shadow: #fff 0px 1px 0px;
+  background-color: #fff;
+  font-size: 11px;
+  font-weight: bold;
+}
+
+div.uploader span.filename {
+  color: #777;
+  width: 82px;
+  border-right: solid 1px #bbb;
+  font-size: 11px;
+}
+
+div.uploader input {
+  width: 190px;
+}
+
+div.uploader.disabled span.action {
+  color: #aaa;
+}
+
+div.uploader.disabled span.filename {
+  border-color: #ddd;
+  color: #aaa;
+}
+/*
+
+CORE FUNCTIONALITY 
+
+Not advised to edit stuff below this line
+-----------------------------------------------------
+*/
+
+.selector, 
+.checker, 
+.button, 
+.radio, 
+.uploader {
+  display: -moz-inline-box;
+  display: inline-block;
+  vertical-align: middle;
+  zoom: 1;
+  *display: inline;
+}
+
+.selector select:focus, .radio input:focus, .checker input:focus, .uploader input:focus {
+  outline: 0;
+}
+
+/* Button */
+
+div.button a,
+div.button button,
+div.button input {
+  position: absolute;
+}
+
+div.button {
+  cursor: pointer;
+  position: relative;
+}
+
+div.button span {
+  display: -moz-inline-box;
+  display: inline-block;
+  line-height: 1;
+  text-align: center;
+}
+
+/* Select */
+
+div.selector {
+  position: relative;
+  padding-left: 10px;
+  overflow: hidden;
+}
+
+div.selector span {
+  display: block;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+div.selector select {
+  position: absolute;
+  opacity: 0;
+  filter: alpha(opacity:0);
+  height: 25px;
+  border: none;
+  background: none;
+}
+
+/* Checker */
+
+div.checker {
+  position: relative;
+}
+
+div.checker span {
+  display: -moz-inline-box;
+  display: inline-block;
+  text-align: center;
+}
+
+div.checker input {
+  opacity: 0;
+  filter: alpha(opacity:0);
+  display: inline-block;
+  background: none;
+}
+
+/* Radio */
+
+div.radio {
+  position: relative;
+}
+
+div.radio span {
+  display: -moz-inline-box;
+  display: inline-block;
+  text-align: center;
+}
+
+div.radio input {
+  opacity: 0;
+  filter: alpha(opacity:0);
+  text-align: center;
+  display: inline-block;
+  background: none;
+}
+
+/* Uploader */
+
+div.uploader {
+  position: relative;
+  overflow: hidden;
+  cursor: default;
+}
+
+div.uploader span.action {
+  float: left;
+  display: inline;
+  padding: 2px 0px;
+  overflow: hidden;
+  cursor: pointer;
+}
+
+div.uploader span.filename {
+  padding: 0px 10px;
+  float: left;
+  display: block;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  cursor: default;
+}
+
+div.uploader input {
+  opacity: 0;
+  filter: alpha(opacity:0);
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  float: right;
+  height: 25px;
+  border: none;
+  cursor: default;
+}
\ No newline at end of file
diff --git a/snf-astakos-app/astakos/im/static/im/images/arrow-down_black.png b/snf-astakos-app/astakos/im/static/im/images/arrow-down_black.png
new file mode 100644 (file)
index 0000000..df23f68
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/arrow-down_black.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/arrow-down_blue.png b/snf-astakos-app/astakos/im/static/im/images/arrow-down_blue.png
new file mode 100644 (file)
index 0000000..687e17e
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/arrow-down_blue.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/arrow-down_grey.png b/snf-astakos-app/astakos/im/static/im/images/arrow-down_grey.png
new file mode 100644 (file)
index 0000000..88127ff
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/arrow-down_grey.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/arrow-up_blue.png b/snf-astakos-app/astakos/im/static/im/images/arrow-up_blue.png
new file mode 100644 (file)
index 0000000..571e3ee
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/arrow-up_blue.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/bandwidth-stats.png b/snf-astakos-app/astakos/im/static/im/images/bandwidth-stats.png
new file mode 100644 (file)
index 0000000..9119d7a
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/bandwidth-stats.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/cpu-stats.png b/snf-astakos-app/astakos/im/static/im/images/cpu-stats.png
new file mode 100644 (file)
index 0000000..00cbf6a
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/cpu-stats.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/create-compute.png b/snf-astakos-app/astakos/im/static/im/images/create-compute.png
new file mode 100644 (file)
index 0000000..4c24a21
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/create-compute.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/create-network.png b/snf-astakos-app/astakos/im/static/im/images/create-network.png
new file mode 100644 (file)
index 0000000..bd4fd88
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/create-network.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/create-resource-cpu.png b/snf-astakos-app/astakos/im/static/im/images/create-resource-cpu.png
new file mode 100644 (file)
index 0000000..a171bff
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/create-resource-cpu.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/create-resource-disk.png b/snf-astakos-app/astakos/im/static/im/images/create-resource-disk.png
new file mode 100644 (file)
index 0000000..0a301a2
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/create-resource-disk.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/create-resource-network.png b/snf-astakos-app/astakos/im/static/im/images/create-resource-network.png
new file mode 100644 (file)
index 0000000..f2a22de
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/create-resource-network.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/create-resource-ram.png b/snf-astakos-app/astakos/im/static/im/images/create-resource-ram.png
new file mode 100644 (file)
index 0000000..6aa42a3
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/create-resource-ram.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/create-resource-storage.png b/snf-astakos-app/astakos/im/static/im/images/create-resource-storage.png
new file mode 100644 (file)
index 0000000..6e2ac00
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/create-resource-storage.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/create-resource-vm.png b/snf-astakos-app/astakos/im/static/im/images/create-resource-vm.png
new file mode 100644 (file)
index 0000000..de78bce
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/create-resource-vm.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/create-storage.png b/snf-astakos-app/astakos/im/static/im/images/create-storage.png
new file mode 100644 (file)
index 0000000..735ae96
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/create-storage.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/create.png b/snf-astakos-app/astakos/im/static/im/images/create.png
new file mode 100644 (file)
index 0000000..1ba78ac
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/create.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/disk-stats.png b/snf-astakos-app/astakos/im/static/im/images/disk-stats.png
new file mode 100644 (file)
index 0000000..24e7ff2
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/disk-stats.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/join.png b/snf-astakos-app/astakos/im/static/im/images/join.png
new file mode 100644 (file)
index 0000000..528ea03
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/join.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/network-stats.png b/snf-astakos-app/astakos/im/static/im/images/network-stats.png
new file mode 100644 (file)
index 0000000..7f5ea1a
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/network-stats.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/plus-minus-hover.png b/snf-astakos-app/astakos/im/static/im/images/plus-minus-hover.png
new file mode 100644 (file)
index 0000000..57d70bb
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/plus-minus-hover.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/plus-minus.png b/snf-astakos-app/astakos/im/static/im/images/plus-minus.png
new file mode 100644 (file)
index 0000000..181ef5c
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/plus-minus.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/quota-related-bg.png b/snf-astakos-app/astakos/im/static/im/images/quota-related-bg.png
new file mode 100644 (file)
index 0000000..b08726a
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/quota-related-bg.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/ram-stats.png b/snf-astakos-app/astakos/im/static/im/images/ram-stats.png
new file mode 100644 (file)
index 0000000..0057178
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/ram-stats.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/red-stats-vm.png b/snf-astakos-app/astakos/im/static/im/images/red-stats-vm.png
new file mode 100644 (file)
index 0000000..7c5b42d
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/red-stats-vm.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/sprite.png b/snf-astakos-app/astakos/im/static/im/images/sprite.png
new file mode 100644 (file)
index 0000000..66b558f
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/sprite.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/statistics_icons.png b/snf-astakos-app/astakos/im/static/im/images/statistics_icons.png
new file mode 100644 (file)
index 0000000..1bc6608
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/statistics_icons.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/stats-line.jpg b/snf-astakos-app/astakos/im/static/im/images/stats-line.jpg
new file mode 100644 (file)
index 0000000..efd88f4
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/stats-line.jpg differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/storage-stats.png b/snf-astakos-app/astakos/im/static/im/images/storage-stats.png
new file mode 100644 (file)
index 0000000..da5fb5d
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/storage-stats.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/symbols2.png b/snf-astakos-app/astakos/im/static/im/images/symbols2.png
new file mode 100644 (file)
index 0000000..5fda343
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/symbols2.png differ
diff --git a/snf-astakos-app/astakos/im/static/im/images/vm-stats.png b/snf-astakos-app/astakos/im/static/im/images/vm-stats.png
new file mode 100644 (file)
index 0000000..d582782
Binary files /dev/null and b/snf-astakos-app/astakos/im/static/im/images/vm-stats.png differ
index b743709..cdc33fa 100644 (file)
@@ -110,9 +110,14 @@ $(document).ready(function() {
              //todo\r
         });\r
     });        \r
-    //$('.dropkick-select').dropkick();\r
     \r
\r
+     \r
+       $('select.dropkicked').dropkick({\r
+               change: function (value, label) {\r
+                   $(this).parents('form').submit();\r
+                   \r
+               }\r
+       });\r
     \r
     $('.top-msg .success').parents('.top-msg').addClass('success');\r
     $('.top-msg .error').parents('.top-msg').addClass('error');\r
@@ -149,62 +154,137 @@ $(document).ready(function() {
     );\r
     \r
     \r
-    /*$('#animation a').hover(\r
-      function () {\r
-       \r
-        $(this).animate({\r
-           top: '+=-10'   \r
-           }, 600);\r
-        $(this).siblings('p').find('img').animate({\r
-          width: '60%'       \r
-        });\r
-      }, \r
-      function () {\r
-\r
-        $(this).animate({top: '0'}, 600);\r
-        $(this).siblings('p').find('img').animate({\r
-          width: '65%'       \r
-        });\r
-      }\r
-    );*/\r
+   \r
     \r
     \r
-    if ($('.widjets'.length > 0)) {\r
-               $('.widjets li div').equalHeights();\r
-       }\r
+    //if ($('.widjets'.length > 0)) {\r
+               ///$('.widjets li div').equalHeights();\r
+       ///}\r
     \r
     $(function() {\r
-       if($("#from").length > 0 ){\r
-                       $( "#from" ).datepicker({\r
+       if($("#id_issue_date").length > 0 ){\r
+                       $( "#id_issue_date" ).datepicker({\r
                                defaultDate: "+0", \r
-                               dateFormat: "dd-mm-yy",\r
+                               dateFormat: "yy-mm-dd",\r
                                onSelect: function( selectedDate ) {\r
-                                       $( "#to" ).datepicker( "option", "minDate", selectedDate );\r
+                                       $( "#id_expiration_date" ).datepicker( "option", "minDate", selectedDate );\r
                                }\r
                        });\r
-                       $( "#to" ).datepicker({\r
+                       $( "#id_expiration_date" ).datepicker({\r
                                defaultDate: "+1w", \r
-                               dateFormat: "dd-mm-yy",\r
+                               dateFormat: "yy-mm-dd",\r
                                onSelect: function( selectedDate ) {\r
-                                       $( "#from" ).datepicker( "option", "maxDate", selectedDate );\r
+                                       $( "#id_issue_date" ).datepicker( "option", "maxDate", selectedDate );\r
                                }\r
                        });\r
                }\r
+               \r
+               if($("#id_issue_date_demo").length > 0 ){\r
+                       $( "#id_issue_date_demo" ).datepicker({\r
+                               defaultDate: "+0", \r
+                               dateFormat: "yy-mm-dd",\r
+                               onSelect: function( selectedDate ) {\r
+                                       $( "#id_expiration_date_demo" ).datepicker( "option", "minDate", selectedDate );\r
+                               }\r
+                       });\r
+                       $( "#id_expiration_date_demo" ).datepicker({\r
+                               defaultDate: "+1w", \r
+                               dateFormat: "yy-mm-dd",\r
+                               onSelect: function( selectedDate ) {\r
+                                       $( "#id_issue_date_demo" ).datepicker( "option", "maxDate", selectedDate );\r
+                               }\r
+                       });\r
+               }\r
+               $( "#id_start_date" ).datepicker({\r
+            dateFormat: "yy-mm-dd",\r
+            onSelect: function( selectedDate ) {\r
+                $( "#id_start_date" ).datepicker( "option", "maxDate", selectedDate );\r
+            }\r
+        });\r
+        \r
+        $( "#id_end_date" ).datepicker({\r
+            dateFormat: "yy-mm-dd",\r
+            onSelect: function( selectedDate ) {\r
+                $( "#id_end_date" ).datepicker( "option", "maxDate", selectedDate );\r
+            }\r
+        });\r
        });\r
-});\r
+       \r
+       \r
+       $(".table_sorting").tablesorter(); \r
+       \r
+       $('table .more-info').click(function(e){\r
+               e.preventDefault();\r
+               $(this).toggleClass('open');\r
+               if ($(this).hasClass('open')){\r
+                       $(this).html('- less info ')\r
+               } else {\r
+                       $(this).html('+ more info ')\r
+               }\r
+               $(this).parents('tr').next('tr').toggle();\r
+                \r
+       });\r
+       \r
+       $('.projects .details .edit').click( function(e){\r
+               e.preventDefault();\r
+               $(this).parents('.details').children('.data').hide();\r
+               $(this).parents('.details').children('.editable').show();\r
+               $(this).hide();\r
+       });\r
+       \r
+       \r
+       $('.widjet-x').click(function(e){\r
+               e.preventDefault();\r
+               $(this).siblings('ul').hide('slow');\r
+               $(this).hide();\r
+       })\r
 \r
+       // todo den doulevei\r
+       $('#group_create_form').submit(function(){\r
+               if ($('.quotas-form .group .form-row.with-errors').length>0 ){\r
+                       return false;\r
+               }\r
+               var flag = 0;\r
+               $('.quotas-form .group input[type="text"]').each(function() {\r
+                       // get value from input\r
+                       var value = $(this).val();\r
+                       if (value){\r
+                               flag =1;\r
+                       }\r
+               });\r
+               if (flag =='0') {\r
+                       $('#icons span.info').addClass('error-msg');\r
+                       return false;\r
+                       \r
+               }\r
+       });\r
+       \r
+       \r
+       \r
+       $("input.leave, input.join").click(function () {\r
+               $(this).parents('.msg-wrap').find('.dialog').show();\r
+               return false;      \r
+               \r
+    });\r
+    \r
+     $('.msg-wrap .no').click( function(e){\r
+               e.preventDefault();\r
+               $(this).parents('.dialog').hide();\r
+       })\r
+    \r
+    $('.msg-wrap .yes').click( function(e){\r
+               e.preventDefault();\r
+               $(this).parents('.dialog').siblings('form').submit();\r
+       })\r
+    \r
+    \r
+});\r
+       \r
 $(window).resize(function() {\r
     \r
    setContainerMinHeight('.container .wrapper');\r
-   if ($('.widjets').length > 0) {\r
-               $('.widjets  li div').equalHeights();\r
-       }\r
-\r
-});\r
-\r
-\r
-\r
-\r
\r
-\r
+   ///if ($('.widjets').length > 0) {\r
+               //$('.widjets  li div').equalHeights();\r
+       //}\r
 \r
+});
\ No newline at end of file
index c271ae2..ee5f153 100644 (file)
@@ -7,9 +7,12 @@
       var $this = $(this);
       var el = $('<a class="checkbox-widget" href="javascript:void(0)"/>');
       var form = $this.closest(".form-row");
+         var className = $this.attr('class');
+         var isRadio = $this.hasClass('radio');
 
       // add class to identify form rows which contain a checkbox
       form.addClass("with-checkbox");
+
       
       if ($this.prev().length > 0) {
         var lbl = $this.prev()[0];
             $(lbl).addClass("checkbox-label");
 
             $(lbl).click(function(e){
+               if (isRadio && $this.attr('checked')){ return; }
                 var src = e.srcElement.nodeName;
                 if (src == "LABEL" || src == "label") {
-                    el.toggleClass("checked");
+                    el.toggleClass("checked"); 
+                    $this.attr('checked', el.hasClass("checked"));
+                    $this.trigger('changed');
                 };
+                
             })
         }
       }
       $this.hide();
       
-      if ($this.attr("checked")) {
+      if ($this.attr('checked')) {
         el.addClass("checked");  
+        
       }
 
+         el.addClass(className);       
+               
       el.click(function() {
+               if (isRadio && $this.attr('checked')){ return; }
         el.toggleClass("checked");
         $this.attr('checked', el.hasClass("checked"));
+        $this.trigger('changed');
       });
       
       el.keypress(function(e){
        
        if (e.keyCode == 0 || e.keyCode == 32){
+               if (isRadio && $this.attr('checked')){ return; }
                e.preventDefault();
                el.toggleClass("checked");
                $this.attr('checked', el.hasClass("checked"));
+               $this.trigger('changed');
        }
       })
 
 
   };
 })( jQuery );
+
+
+
diff --git a/snf-astakos-app/astakos/im/static/im/js/jquery-ui-1.8.21.custom.min.js b/snf-astakos-app/astakos/im/static/im/js/jquery-ui-1.8.21.custom.min.js
new file mode 100644 (file)
index 0000000..3fe9ccb
--- /dev/null
@@ -0,0 +1,125 @@
+/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.core.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){function c(b,c){var e=b.nodeName.toLowerCase();if("area"===e){var f=b.parentNode,g=f.name,h;return!b.href||!g||f.nodeName.toLowerCase()!=="map"?!1:(h=a("img[usemap=#"+g+"]")[0],!!h&&d(h))}return(/input|select|textarea|button|object/.test(e)?!b.disabled:"a"==e?b.href||c:c)&&d(b)}function d(b){return!a(b).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}a.ui=a.ui||{};if(a.ui.version)return;a.extend(a.ui,{version:"1.8.21",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}}),a.fn.extend({propAttr:a.fn.prop||a.fn.attr,_focus:a.fn.focus,focus:function(b,c){return typeof b=="number"?this.each(function(){var d=this;setTimeout(function(){a(d).focus(),c&&c.call(d)},b)}):this._focus.apply(this,arguments)},scrollParent:function(){var b;return a.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?b=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(a.curCSS(this,"position",1))&&/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0):b=this.parents().filter(function(){return/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0),/fixed/.test(this.css("position"))||!b.length?a(document):b},zIndex:function(c){if(c!==b)return this.css("zIndex",c);if(this.length){var d=a(this[0]),e,f;while(d.length&&d[0]!==document){e=d.css("position");if(e==="absolute"||e==="relative"||e==="fixed"){f=parseInt(d.css("zIndex"),10);if(!isNaN(f)&&f!==0)return f}d=d.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),a.each(["Width","Height"],function(c,d){function h(b,c,d,f){return a.each(e,function(){c-=parseFloat(a.curCSS(b,"padding"+this,!0))||0,d&&(c-=parseFloat(a.curCSS(b,"border"+this+"Width",!0))||0),f&&(c-=parseFloat(a.curCSS(b,"margin"+this,!0))||0)}),c}var e=d==="Width"?["Left","Right"]:["Top","Bottom"],f=d.toLowerCase(),g={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};a.fn["inner"+d]=function(c){return c===b?g["inner"+d].call(this):this.each(function(){a(this).css(f,h(this,c)+"px")})},a.fn["outer"+d]=function(b,c){return typeof b!="number"?g["outer"+d].call(this,b):this.each(function(){a(this).css(f,h(this,b,!0,c)+"px")})}}),a.extend(a.expr[":"],{data:function(b,c,d){return!!a.data(b,d[3])},focusable:function(b){return c(b,!isNaN(a.attr(b,"tabindex")))},tabbable:function(b){var d=a.attr(b,"tabindex"),e=isNaN(d);return(e||d>=0)&&c(b,!e)}}),a(function(){var b=document.body,c=b.appendChild(c=document.createElement("div"));c.offsetHeight,a.extend(c.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),a.support.minHeight=c.offsetHeight===100,a.support.selectstart="onselectstart"in c,b.removeChild(c).style.display="none"}),a.extend(a.ui,{plugin:{add:function(b,c,d){var e=a.ui[b].prototype;for(var f in d)e.plugins[f]=e.plugins[f]||[],e.plugins[f].push([c,d[f]])},call:function(a,b,c){var d=a.plugins[b];if(!d||!a.element[0].parentNode)return;for(var e=0;e<d.length;e++)a.options[d[e][0]]&&d[e][1].apply(a.element,c)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(b,c){if(a(b).css("overflow")==="hidden")return!1;var d=c&&c==="left"?"scrollLeft":"scrollTop",e=!1;return b[d]>0?!0:(b[d]=1,e=b[d]>0,b[d]=0,e)},isOverAxis:function(a,b,c){return a>b&&a<b+c},isOver:function(b,c,d,e,f,g){return a.ui.isOverAxis(b,d,f)&&a.ui.isOverAxis(c,e,g)}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.widget.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){if(a.cleanData){var c=a.cleanData;a.cleanData=function(b){for(var d=0,e;(e=b[d])!=null;d++)try{a(e).triggerHandler("remove")}catch(f){}c(b)}}else{var d=a.fn.remove;a.fn.remove=function(b,c){return this.each(function(){return c||(!b||a.filter(b,[this]).length)&&a("*",this).add([this]).each(function(){try{a(this).triggerHandler("remove")}catch(b){}}),d.call(a(this),b,c)})}}a.widget=function(b,c,d){var e=b.split(".")[0],f;b=b.split(".")[1],f=e+"-"+b,d||(d=c,c=a.Widget),a.expr[":"][f]=function(c){return!!a.data(c,b)},a[e]=a[e]||{},a[e][b]=function(a,b){arguments.length&&this._createWidget(a,b)};var g=new c;g.options=a.extend(!0,{},g.options),a[e][b].prototype=a.extend(!0,g,{namespace:e,widgetName:b,widgetEventPrefix:a[e][b].prototype.widgetEventPrefix||b,widgetBaseClass:f},d),a.widget.bridge(b,a[e][b])},a.widget.bridge=function(c,d){a.fn[c]=function(e){var f=typeof e=="string",g=Array.prototype.slice.call(arguments,1),h=this;return e=!f&&g.length?a.extend.apply(null,[!0,e].concat(g)):e,f&&e.charAt(0)==="_"?h:(f?this.each(function(){var d=a.data(this,c),f=d&&a.isFunction(d[e])?d[e].apply(d,g):d;if(f!==d&&f!==b)return h=f,!1}):this.each(function(){var b=a.data(this,c);b?b.option(e||{})._init():a.data(this,c,new d(e,this))}),h)}},a.Widget=function(a,b){arguments.length&&this._createWidget(a,b)},a.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:!1},_createWidget:function(b,c){a.data(c,this.widgetName,this),this.element=a(c),this.options=a.extend(!0,{},this.options,this._getCreateOptions(),b);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()}),this._create(),this._trigger("create"),this._init()},_getCreateOptions:function(){return a.metadata&&a.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName),this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled "+"ui-state-disabled")},widget:function(){return this.element},option:function(c,d){var e=c;if(arguments.length===0)return a.extend({},this.options);if(typeof c=="string"){if(d===b)return this.options[c];e={},e[c]=d}return this._setOptions(e),this},_setOptions:function(b){var c=this;return a.each(b,function(a,b){c._setOption(a,b)}),this},_setOption:function(a,b){return this.options[a]=b,a==="disabled"&&this.widget()[b?"addClass":"removeClass"](this.widgetBaseClass+"-disabled"+" "+"ui-state-disabled").attr("aria-disabled",b),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_trigger:function(b,c,d){var e,f,g=this.options[b];d=d||{},c=a.Event(c),c.type=(b===this.widgetEventPrefix?b:this.widgetEventPrefix+b).toLowerCase(),c.target=this.element[0],f=c.originalEvent;if(f)for(e in f)e in c||(c[e]=f[e]);return this.element.trigger(c,d),!(a.isFunction(g)&&g.call(this.element[0],c,d)===!1||c.isDefaultPrevented())}}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.mouse.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){var c=!1;a(document).mouseup(function(a){c=!1}),a.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var b=this;this.element.bind("mousedown."+this.widgetName,function(a){return b._mouseDown(a)}).bind("click."+this.widgetName,function(c){if(!0===a.data(c.target,b.widgetName+".preventClickEvent"))return a.removeData(c.target,b.widgetName+".preventClickEvent"),c.stopImmediatePropagation(),!1}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),a(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(b){if(c)return;this._mouseStarted&&this._mouseUp(b),this._mouseDownEvent=b;var d=this,e=b.which==1,f=typeof this.options.cancel=="string"&&b.target.nodeName?a(b.target).closest(this.options.cancel).length:!1;if(!e||f||!this._mouseCapture(b))return!0;this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){d.mouseDelayMet=!0},this.options.delay));if(this._mouseDistanceMet(b)&&this._mouseDelayMet(b)){this._mouseStarted=this._mouseStart(b)!==!1;if(!this._mouseStarted)return b.preventDefault(),!0}return!0===a.data(b.target,this.widgetName+".preventClickEvent")&&a.removeData(b.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(a){return d._mouseMove(a)},this._mouseUpDelegate=function(a){return d._mouseUp(a)},a(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),b.preventDefault(),c=!0,!0},_mouseMove:function(b){return!a.browser.msie||document.documentMode>=9||!!b.button?this._mouseStarted?(this._mouseDrag(b),b.preventDefault()):(this._mouseDistanceMet(b)&&this._mouseDelayMet(b)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,b)!==!1,this._mouseStarted?this._mouseDrag(b):this._mouseUp(b)),!this._mouseStarted):this._mouseUp(b)},_mouseUp:function(b){return a(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,b.target==this._mouseDownEvent.target&&a.data(b.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(b)),!1},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(a){return this.mouseDelayMet},_mouseStart:function(a){},_mouseDrag:function(a){},_mouseStop:function(a){},_mouseCapture:function(a){return!0}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.position.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.ui=a.ui||{};var c=/left|center|right/,d=/top|center|bottom/,e="center",f={},g=a.fn.position,h=a.fn.offset;a.fn.position=function(b){if(!b||!b.of)return g.apply(this,arguments);b=a.extend({},b);var h=a(b.of),i=h[0],j=(b.collision||"flip").split(" "),k=b.offset?b.offset.split(" "):[0,0],l,m,n;return i.nodeType===9?(l=h.width(),m=h.height(),n={top:0,left:0}):i.setTimeout?(l=h.width(),m=h.height(),n={top:h.scrollTop(),left:h.scrollLeft()}):i.preventDefault?(b.at="left top",l=m=0,n={top:b.of.pageY,left:b.of.pageX}):(l=h.outerWidth(),m=h.outerHeight(),n=h.offset()),a.each(["my","at"],function(){var a=(b[this]||"").split(" ");a.length===1&&(a=c.test(a[0])?a.concat([e]):d.test(a[0])?[e].concat(a):[e,e]),a[0]=c.test(a[0])?a[0]:e,a[1]=d.test(a[1])?a[1]:e,b[this]=a}),j.length===1&&(j[1]=j[0]),k[0]=parseInt(k[0],10)||0,k.length===1&&(k[1]=k[0]),k[1]=parseInt(k[1],10)||0,b.at[0]==="right"?n.left+=l:b.at[0]===e&&(n.left+=l/2),b.at[1]==="bottom"?n.top+=m:b.at[1]===e&&(n.top+=m/2),n.left+=k[0],n.top+=k[1],this.each(function(){var c=a(this),d=c.outerWidth(),g=c.outerHeight(),h=parseInt(a.curCSS(this,"marginLeft",!0))||0,i=parseInt(a.curCSS(this,"marginTop",!0))||0,o=d+h+(parseInt(a.curCSS(this,"marginRight",!0))||0),p=g+i+(parseInt(a.curCSS(this,"marginBottom",!0))||0),q=a.extend({},n),r;b.my[0]==="right"?q.left-=d:b.my[0]===e&&(q.left-=d/2),b.my[1]==="bottom"?q.top-=g:b.my[1]===e&&(q.top-=g/2),f.fractions||(q.left=Math.round(q.left),q.top=Math.round(q.top)),r={left:q.left-h,top:q.top-i},a.each(["left","top"],function(c,e){a.ui.position[j[c]]&&a.ui.position[j[c]][e](q,{targetWidth:l,targetHeight:m,elemWidth:d,elemHeight:g,collisionPosition:r,collisionWidth:o,collisionHeight:p,offset:k,my:b.my,at:b.at})}),a.fn.bgiframe&&c.bgiframe(),c.offset(a.extend(q,{using:b.using}))})},a.ui.position={fit:{left:function(b,c){var d=a(window),e=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft();b.left=e>0?b.left-e:Math.max(b.left-c.collisionPosition.left,b.left)},top:function(b,c){var d=a(window),e=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop();b.top=e>0?b.top-e:Math.max(b.top-c.collisionPosition.top,b.top)}},flip:{left:function(b,c){if(c.at[0]===e)return;var d=a(window),f=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft(),g=c.my[0]==="left"?-c.elemWidth:c.my[0]==="right"?c.elemWidth:0,h=c.at[0]==="left"?c.targetWidth:-c.targetWidth,i=-2*c.offset[0];b.left+=c.collisionPosition.left<0?g+h+i:f>0?g+h+i:0},top:function(b,c){if(c.at[1]===e)return;var d=a(window),f=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop(),g=c.my[1]==="top"?-c.elemHeight:c.my[1]==="bottom"?c.elemHeight:0,h=c.at[1]==="top"?c.targetHeight:-c.targetHeight,i=-2*c.offset[1];b.top+=c.collisionPosition.top<0?g+h+i:f>0?g+h+i:0}}},a.offset.setOffset||(a.offset.setOffset=function(b,c){/static/.test(a.curCSS(b,"position"))&&(b.style.position="relative");var d=a(b),e=d.offset(),f=parseInt(a.curCSS(b,"top",!0),10)||0,g=parseInt(a.curCSS(b,"left",!0),10)||0,h={top:c.top-e.top+f,left:c.left-e.left+g};"using"in c?c.using.call(b,h):d.css(h)},a.fn.offset=function(b){var c=this[0];return!c||!c.ownerDocument?null:b?a.isFunction(b)?this.each(function(c){a(this).offset(b.call(this,c,a(this).offset()))}):this.each(function(){a.offset.setOffset(this,b)}):h.call(this)}),function(){var b=document.getElementsByTagName("body")[0],c=document.createElement("div"),d,e,g,h,i;d=document.createElement(b?"div":"body"),g={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},b&&a.extend(g,{position:"absolute",left:"-1000px",top:"-1000px"});for(var j in g)d.style[j]=g[j];d.appendChild(c),e=b||document.documentElement,e.insertBefore(d,e.firstChild),c.style.cssText="position: absolute; left: 10.7432222px; top: 10.432325px; height: 30px; width: 201px;",h=a(c).offset(function(a,b){return b}).offset(),d.innerHTML="",e.removeChild(d),i=h.top+h.left+(b?2e3:0),f.fractions=i>21&&i<22}()})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.draggable.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.widget("ui.draggable",a.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1},_create:function(){this.options.helper=="original"&&!/^(?:r|a|f)/.test(this.element.css("position"))&&(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},destroy:function(){if(!this.element.data("draggable"))return;return this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy(),this},_mouseCapture:function(b){var c=this.options;return this.helper||c.disabled||a(b.target).is(".ui-resizable-handle")?!1:(this.handle=this._getHandle(b),this.handle?(c.iframeFix&&a(c.iframeFix===!0?"iframe":c.iframeFix).each(function(){a('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(a(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(b){var c=this.options;return this.helper=this._createHelper(b),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),a.ui.ddmanager&&(a.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(b),this.originalPageX=b.pageX,this.originalPageY=b.pageY,c.cursorAt&&this._adjustOffsetFromHelper(c.cursorAt),c.containment&&this._setContainment(),this._trigger("start",b)===!1?(this._clear(),!1):(this._cacheHelperProportions(),a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b),this._mouseDrag(b,!0),a.ui.ddmanager&&a.ui.ddmanager.dragStart(this,b),!0)},_mouseDrag:function(b,c){this.position=this._generatePosition(b),this.positionAbs=this._convertPositionTo("absolute");if(!c){var d=this._uiHash();if(this._trigger("drag",b,d)===!1)return this._mouseUp({}),!1;this.position=d.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";return a.ui.ddmanager&&a.ui.ddmanager.drag(this,b),!1},_mouseStop:function(b){var c=!1;a.ui.ddmanager&&!this.options.dropBehaviour&&(c=a.ui.ddmanager.drop(this,b)),this.dropped&&(c=this.dropped,this.dropped=!1);var d=this.element[0],e=!1;while(d&&(d=d.parentNode))d==document&&(e=!0);if(!e&&this.options.helper==="original")return!1;if(this.options.revert=="invalid"&&!c||this.options.revert=="valid"&&c||this.options.revert===!0||a.isFunction(this.options.revert)&&this.options.revert.call(this.element,c)){var f=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){f._trigger("stop",b)!==!1&&f._clear()})}else this._trigger("stop",b)!==!1&&this._clear();return!1},_mouseUp:function(b){return this.options.iframeFix===!0&&a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),a.ui.ddmanager&&a.ui.ddmanager.dragStop(this,b),a.ui.mouse.prototype._mouseUp.call(this,b)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(b){var c=!this.options.handle||!a(this.options.handle,this.element).length?!0:!1;return a(this.options.handle,this.element).find("*").andSelf().each(function(){this==b.target&&(c=!0)}),c},_createHelper:function(b){var c=this.options,d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[b])):c.helper=="clone"?this.element.clone().removeAttr("id"):this.element;return d.parents("body").length||d.appendTo(c.appendTo=="parent"?this.element[0].parentNode:c.appendTo),d[0]!=this.element[0]&&!/(fixed|absolute)/.test(d.css("position"))&&d.css("position","absolute"),d},_adjustOffsetFromHelper:function(b){typeof b=="string"&&(b=b.split(" ")),a.isArray(b)&&(b={left:+b[0],top:+b[1]||0}),"left"in b&&(this.offset.click.left=b.left+this.margins.left),"right"in b&&(this.offset.click.left=this.helperProportions.width-b.right+this.margins.left),"top"in b&&(this.offset.click.top=b.top+this.margins.top),"bottom"in b&&(this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])&&(b.left+=this.scrollParent.scrollLeft(),b.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)b={top:0,left:0};return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.element.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var b=this.options;b.containment=="parent"&&(b.containment=this.helper[0].parentNode);if(b.containment=="document"||b.containment=="window")this.containment=[b.containment=="document"?0:a(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,b.containment=="document"?0:a(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,(b.containment=="document"?0:a(window).scrollLeft())+a(b.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(b.containment=="document"?0:a(window).scrollTop())+(a(b.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(b.containment)&&b.containment.constructor!=Array){var c=a(b.containment),d=c[0];if(!d)return;var e=c.offset(),f=a(d).css("overflow")!="hidden";this.containment=[(parseInt(a(d).css("borderLeftWidth"),10)||0)+(parseInt(a(d).css("paddingLeft"),10)||0),(parseInt(a(d).css("borderTopWidth"),10)||0)+(parseInt(a(d).css("paddingTop"),10)||0),(f?Math.max(d.scrollWidth,d.offsetWidth):d.offsetWidth)-(parseInt(a(d).css("borderLeftWidth"),10)||0)-(parseInt(a(d).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(f?Math.max(d.scrollHeight,d.offsetHeight):d.offsetHeight)-(parseInt(a(d).css("borderTopWidth"),10)||0)-(parseInt(a(d).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=c}else b.containment.constructor==Array&&(this.containment=b.containment)},_convertPositionTo:function(b,c){c||(c=this.position);var d=b=="absolute"?1:-1,e=this.options,f=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())*d)}},_generatePosition:function(b){var c=this.options,d=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(d[0].tagName),f=b.pageX,g=b.pageY;if(this.originalPosition){var h;if(this.containment){if(this.relative_container){var i=this.relative_container.offset();h=[this.containment[0]+i.left,this.containment[1]+i.top,this.containment[2]+i.left,this.containment[3]+i.top]}else h=this.containment;b.pageX-this.offset.click.left<h[0]&&(f=h[0]+this.offset.click.left),b.pageY-this.offset.click.top<h[1]&&(g=h[1]+this.offset.click.top),b.pageX-this.offset.click.left>h[2]&&(f=h[2]+this.offset.click.left),b.pageY-this.offset.click.top>h[3]&&(g=h[3]+this.offset.click.top)}if(c.grid){var j=c.grid[1]?this.originalPageY+Math.round((g-this.originalPageY)/c.grid[1])*c.grid[1]:this.originalPageY;g=h?j-this.offset.click.top<h[1]||j-this.offset.click.top>h[3]?j-this.offset.click.top<h[1]?j+c.grid[1]:j-c.grid[1]:j:j;var k=c.grid[0]?this.originalPageX+Math.round((f-this.originalPageX)/c.grid[0])*c.grid[0]:this.originalPageX;f=h?k-this.offset.click.left<h[0]||k-this.offset.click.left>h[2]?k-this.offset.click.left<h[0]?k+c.grid[0]:k-c.grid[0]:k:k}}return{top:g-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():e?0:d.scrollTop()),left:f-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():e?0:d.scrollLeft())}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"),this.helper[0]!=this.element[0]&&!this.cancelHelperRemoval&&this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1},_trigger:function(b,c,d){return d=d||this._uiHash(),a.ui.plugin.call(this,b,[c,d]),b=="drag"&&(this.positionAbs=this._convertPositionTo("absolute")),a.Widget.prototype._trigger.call(this,b,c,d)},plugins:{},_uiHash:function(a){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),a.extend(a.ui.draggable,{version:"1.8.21"}),a.ui.plugin.add("draggable","connectToSortable",{start:function(b,c){var d=a(this).data("draggable"),e=d.options,f=a.extend({},c,{item:d.element});d.sortables=[],a(e.connectToSortable).each(function(){var c=a.data(this,"sortable");c&&!c.options.disabled&&(d.sortables.push({instance:c,shouldRevert:c.options.revert}),c.refreshPositions(),c._trigger("activate",b,f))})},stop:function(b,c){var d=a(this).data("draggable"),e=a.extend({},c,{item:d.element});a.each(d.sortables,function(){this.instance.isOver?(this.instance.isOver=0,d.cancelHelperRemoval=!0,this.instance.cancelHelperRemoval=!1,this.shouldRevert&&(this.instance.options.revert=!0),this.instance._mouseStop(b),this.instance.options.helper=this.instance.options._helper,d.options.helper=="original"&&this.instance.currentItem.css({top:"auto",left:"auto"})):(this.instance.cancelHelperRemoval=!1,this.instance._trigger("deactivate",b,e))})},drag:function(b,c){var d=a(this).data("draggable"),e=this,f=function(b){var c=this.offset.click.top,d=this.offset.click.left,e=this.positionAbs.top,f=this.positionAbs.left,g=b.height,h=b.width,i=b.top,j=b.left;return a.ui.isOver(e+c,f+d,i,j,g,h)};a.each(d.sortables,function(f){this.instance.positionAbs=d.positionAbs,this.instance.helperProportions=d.helperProportions,this.instance.offset.click=d.offset.click,this.instance._intersectsWith(this.instance.containerCache)?(this.instance.isOver||(this.instance.isOver=1,this.instance.currentItem=a(e).clone().removeAttr("id").appendTo(this.instance.element).data("sortable-item",!0),this.instance.options._helper=this.instance.options.helper,this.instance.options.helper=function(){return c.helper[0]},b.target=this.instance.currentItem[0],this.instance._mouseCapture(b,!0),this.instance._mouseStart(b,!0,!0),this.instance.offset.click.top=d.offset.click.top,this.instance.offset.click.left=d.offset.click.left,this.instance.offset.parent.left-=d.offset.parent.left-this.instance.offset.parent.left,this.instance.offset.parent.top-=d.offset.parent.top-this.instance.offset.parent.top,d._trigger("toSortable",b),d.dropped=this.instance.element,d.currentItem=d.element,this.instance.fromOutside=d),this.instance.currentItem&&this.instance._mouseDrag(b)):this.instance.isOver&&(this.instance.isOver=0,this.instance.cancelHelperRemoval=!0,this.instance.options.revert=!1,this.instance._trigger("out",b,this.instance._uiHash(this.instance)),this.instance._mouseStop(b,!0),this.instance.options.helper=this.instance.options._helper,this.instance.currentItem.remove(),this.instance.placeholder&&this.instance.placeholder.remove(),d._trigger("fromSortable",b),d.dropped=!1)})}}),a.ui.plugin.add("draggable","cursor",{start:function(b,c){var d=a("body"),e=a(this).data("draggable").options;d.css("cursor")&&(e._cursor=d.css("cursor")),d.css("cursor",e.cursor)},stop:function(b,c){var d=a(this).data("draggable").options;d._cursor&&a("body").css("cursor",d._cursor)}}),a.ui.plugin.add("draggable","opacity",{start:function(b,c){var d=a(c.helper),e=a(this).data("draggable").options;d.css("opacity")&&(e._opacity=d.css("opacity")),d.css("opacity",e.opacity)},stop:function(b,c){var d=a(this).data("draggable").options;d._opacity&&a(c.helper).css("opacity",d._opacity)}}),a.ui.plugin.add("draggable","scroll",{start:function(b,c){var d=a(this).data("draggable");d.scrollParent[0]!=document&&d.scrollParent[0].tagName!="HTML"&&(d.overflowOffset=d.scrollParent.offset())},drag:function(b,c){var d=a(this).data("draggable"),e=d.options,f=!1;if(d.scrollParent[0]!=document&&d.scrollParent[0].tagName!="HTML"){if(!e.axis||e.axis!="x")d.overflowOffset.top+d.scrollParent[0].offsetHeight-b.pageY<e.scrollSensitivity?d.scrollParent[0].scrollTop=f=d.scrollParent[0].scrollTop+e.scrollSpeed:b.pageY-d.overflowOffset.top<e.scrollSensitivity&&(d.scrollParent[0].scrollTop=f=d.scrollParent[0].scrollTop-e.scrollSpeed);if(!e.axis||e.axis!="y")d.overflowOffset.left+d.scrollParent[0].offsetWidth-b.pageX<e.scrollSensitivity?d.scrollParent[0].scrollLeft=f=d.scrollParent[0].scrollLeft+e.scrollSpeed:b.pageX-d.overflowOffset.left<e.scrollSensitivity&&(d.scrollParent[0].scrollLeft=f=d.scrollParent[0].scrollLeft-e.scrollSpeed)}else{if(!e.axis||e.axis!="x")b.pageY-a(document).scrollTop()<e.scrollSensitivity?f=a(document).scrollTop(a(document).scrollTop()-e.scrollSpeed):a(window).height()-(b.pageY-a(document).scrollTop())<e.scrollSensitivity&&(f=a(document).scrollTop(a(document).scrollTop()+e.scrollSpeed));if(!e.axis||e.axis!="y")b.pageX-a(document).scrollLeft()<e.scrollSensitivity?f=a(document).scrollLeft(a(document).scrollLeft()-e.scrollSpeed):a(window).width()-(b.pageX-a(document).scrollLeft())<e.scrollSensitivity&&(f=a(document).scrollLeft(a(document).scrollLeft()+e.scrollSpeed))}f!==!1&&a.ui.ddmanager&&!e.dropBehaviour&&a.ui.ddmanager.prepareOffsets(d,b)}}),a.ui.plugin.add("draggable","snap",{start:function(b,c){var d=a(this).data("draggable"),e=d.options;d.snapElements=[],a(e.snap.constructor!=String?e.snap.items||":data(draggable)":e.snap).each(function(){var b=a(this),c=b.offset();this!=d.element[0]&&d.snapElements.push({item:this,width:b.outerWidth(),height:b.outerHeight(),top:c.top,left:c.left})})},drag:function(b,c){var d=a(this).data("draggable"),e=d.options,f=e.snapTolerance,g=c.offset.left,h=g+d.helperProportions.width,i=c.offset.top,j=i+d.helperProportions.height;for(var k=d.snapElements.length-1;k>=0;k--){var l=d.snapElements[k].left,m=l+d.snapElements[k].width,n=d.snapElements[k].top,o=n+d.snapElements[k].height;if(!(l-f<g&&g<m+f&&n-f<i&&i<o+f||l-f<g&&g<m+f&&n-f<j&&j<o+f||l-f<h&&h<m+f&&n-f<i&&i<o+f||l-f<h&&h<m+f&&n-f<j&&j<o+f)){d.snapElements[k].snapping&&d.options.snap.release&&d.options.snap.release.call(d.element,b,a.extend(d._uiHash(),{snapItem:d.snapElements[k].item})),d.snapElements[k].snapping=!1;continue}if(e.snapMode!="inner"){var p=Math.abs(n-j)<=f,q=Math.abs(o-i)<=f,r=Math.abs(l-h)<=f,s=Math.abs(m-g)<=f;p&&(c.position.top=d._convertPositionTo("relative",{top:n-d.helperProportions.height,left:0}).top-d.margins.top),q&&(c.position.top=d._convertPositionTo("relative",{top:o,left:0}).top-d.margins.top),r&&(c.position.left=d._convertPositionTo("relative",{top:0,left:l-d.helperProportions.width}).left-d.margins.left),s&&(c.position.left=d._convertPositionTo("relative",{top:0,left:m}).left-d.margins.left)}var t=p||q||r||s;if(e.snapMode!="outer"){var p=Math.abs(n-i)<=f,q=Math.abs(o-j)<=f,r=Math.abs(l-g)<=f,s=Math.abs(m-h)<=f;p&&(c.position.top=d._convertPositionTo("relative",{top:n,left:0}).top-d.margins.top),q&&(c.position.top=d._convertPositionTo("relative",{top:o-d.helperProportions.height,left:0}).top-d.margins.top),r&&(c.position.left=d._convertPositionTo("relative",{top:0,left:l}).left-d.margins.left),s&&(c.position.left=d._convertPositionTo("relative",{top:0,left:m-d.helperProportions.width}).left-d.margins.left)}!d.snapElements[k].snapping&&(p||q||r||s||t)&&d.options.snap.snap&&d.options.snap.snap.call(d.element,b,a.extend(d._uiHash(),{snapItem:d.snapElements[k].item})),d.snapElements[k].snapping=p||q||r||s||t}}}),a.ui.plugin.add("draggable","stack",{start:function(b,c){var d=a(this).data("draggable").options,e=a.makeArray(a(d.stack)).sort(function(b,c){return(parseInt(a(b).css("zIndex"),10)||0)-(parseInt(a(c).css("zIndex"),10)||0)});if(!e.length)return;var f=parseInt(e[0].style.zIndex)||0;a(e).each(function(a){this.style.zIndex=f+a}),this[0].style.zIndex=f+e.length}}),a.ui.plugin.add("draggable","zIndex",{start:function(b,c){var d=a(c.helper),e=a(this).data("draggable").options;d.css("zIndex")&&(e._zIndex=d.css("zIndex")),d.css("zIndex",e.zIndex)},stop:function(b,c){var d=a(this).data("draggable").options;d._zIndex&&a(c.helper).css("zIndex",d._zIndex)}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.droppable.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.widget("ui.droppable",{widgetEventPrefix:"drop",options:{accept:"*",activeClass:!1,addClasses:!0,greedy:!1,hoverClass:!1,scope:"default",tolerance:"intersect"},_create:function(){var b=this.options,c=b.accept;this.isover=0,this.isout=1,this.accept=a.isFunction(c)?c:function(a){return a.is(c)},this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight},a.ui.ddmanager.droppables[b.scope]=a.ui.ddmanager.droppables[b.scope]||[],a.ui.ddmanager.droppables[b.scope].push(this),b.addClasses&&this.element.addClass("ui-droppable")},destroy:function(){var b=a.ui.ddmanager.droppables[this.options.scope];for(var c=0;c<b.length;c++)b[c]==this&&b.splice(c,1);return this.element.removeClass("ui-droppable ui-droppable-disabled").removeData("droppable").unbind(".droppable"),this},_setOption:function(b,c){b=="accept"&&(this.accept=a.isFunction(c)?c:function(a){return a.is(c)}),a.Widget.prototype._setOption.apply(this,arguments)},_activate:function(b){var c=a.ui.ddmanager.current;this.options.activeClass&&this.element.addClass(this.options.activeClass),c&&this._trigger("activate",b,this.ui(c))},_deactivate:function(b){var c=a.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass),c&&this._trigger("deactivate",b,this.ui(c))},_over:function(b){var c=a.ui.ddmanager.current;if(!c||(c.currentItem||c.element)[0]==this.element[0])return;this.accept.call(this.element[0],c.currentItem||c.element)&&(this.options.hoverClass&&this.element.addClass(this.options.hoverClass),this._trigger("over",b,this.ui(c)))},_out:function(b){var c=a.ui.ddmanager.current;if(!c||(c.currentItem||c.element)[0]==this.element[0])return;this.accept.call(this.element[0],c.currentItem||c.element)&&(this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("out",b,this.ui(c)))},_drop:function(b,c){var d=c||a.ui.ddmanager.current;if(!d||(d.currentItem||d.element)[0]==this.element[0])return!1;var e=!1;return this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function(){var b=a.data(this,"droppable");if(b.options.greedy&&!b.options.disabled&&b.options.scope==d.options.scope&&b.accept.call(b.element[0],d.currentItem||d.element)&&a.ui.intersect(d,a.extend(b,{offset:b.element.offset()}),b.options.tolerance))return e=!0,!1}),e?!1:this.accept.call(this.element[0],d.currentItem||d.element)?(this.options.activeClass&&this.element.removeClass(this.options.activeClass),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("drop",b,this.ui(d)),this.element):!1},ui:function(a){return{draggable:a.currentItem||a.element,helper:a.helper,position:a.position,offset:a.positionAbs}}}),a.extend(a.ui.droppable,{version:"1.8.21"}),a.ui.intersect=function(b,c,d){if(!c.offset)return!1;var e=(b.positionAbs||b.position.absolute).left,f=e+b.helperProportions.width,g=(b.positionAbs||b.position.absolute).top,h=g+b.helperProportions.height,i=c.offset.left,j=i+c.proportions.width,k=c.offset.top,l=k+c.proportions.height;switch(d){case"fit":return i<=e&&f<=j&&k<=g&&h<=l;case"intersect":return i<e+b.helperProportions.width/2&&f-b.helperProportions.width/2<j&&k<g+b.helperProportions.height/2&&h-b.helperProportions.height/2<l;case"pointer":var m=(b.positionAbs||b.position.absolute).left+(b.clickOffset||b.offset.click).left,n=(b.positionAbs||b.position.absolute).top+(b.clickOffset||b.offset.click).top,o=a.ui.isOver(n,m,k,i,c.proportions.height,c.proportions.width);return o;case"touch":return(g>=k&&g<=l||h>=k&&h<=l||g<k&&h>l)&&(e>=i&&e<=j||f>=i&&f<=j||e<i&&f>j);default:return!1}},a.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(b,c){var d=a.ui.ddmanager.droppables[b.options.scope]||[],e=c?c.type:null,f=(b.currentItem||b.element).find(":data(droppable)").andSelf();g:for(var h=0;h<d.length;h++){if(d[h].options.disabled||b&&!d[h].accept.call(d[h].element[0],b.currentItem||b.element))continue;for(var i=0;i<f.length;i++)if(f[i]==d[h].element[0]){d[h].proportions.height=0;continue g}d[h].visible=d[h].element.css("display")!="none";if(!d[h].visible)continue;e=="mousedown"&&d[h]._activate.call(d[h],c),d[h].offset=d[h].element.offset(),d[h].proportions={width:d[h].element[0].offsetWidth,height:d[h].element[0].offsetHeight}}},drop:function(b,c){var d=!1;return a.each(a.ui.ddmanager.droppables[b.options.scope]||[],function(){if(!this.options)return;!this.options.disabled&&this.visible&&a.ui.intersect(b,this,this.options.tolerance)&&(d=this._drop.call(this,c)||d),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],b.currentItem||b.element)&&(this.isout=1,this.isover=0,this._deactivate.call(this,c))}),d},dragStart:function(b,c){b.element.parents(":not(body,html)").bind("scroll.droppable",function(){b.options.refreshPositions||a.ui.ddmanager.prepareOffsets(b,c)})},drag:function(b,c){b.options.refreshPositions&&a.ui.ddmanager.prepareOffsets(b,c),a.each(a.ui.ddmanager.droppables[b.options.scope]||[],function(){if(this.options.disabled||this.greedyChild||!this.visible)return;var d=a.ui.intersect(b,this,this.options.tolerance),e=!d&&this.isover==1?"isout":d&&this.isover==0?"isover":null;if(!e)return;var f;if(this.options.greedy){var g=this.element.parents(":data(droppable):eq(0)");g.length&&(f=a.data(g[0],"droppable"),f.greedyChild=e=="isover"?1:0)}f&&e=="isover"&&(f.isover=0,f.isout=1,f._out.call(f,c)),this[e]=1,this[e=="isout"?"isover":"isout"]=0,this[e=="isover"?"_over":"_out"].call(this,c),f&&e=="isout"&&(f.isout=0,f.isover=1,f._over.call(f,c))})},dragStop:function(b,c){b.element.parents(":not(body,html)").unbind("scroll.droppable"),b.options.refreshPositions||a.ui.ddmanager.prepareOffsets(b,c)}}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.resizable.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.widget("ui.resizable",a.ui.mouse,{widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1e3},_create:function(){var b=this,c=this.options;this.element.addClass("ui-resizable"),a.extend(this,{_aspectRatio:!!c.aspectRatio,aspectRatio:c.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:c.helper||c.ghost||c.animate?c.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)&&(this.element.wrap(a('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("resizable",this.element.data("resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=c.handles||(a(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se");if(this.handles.constructor==String){this.handles=="all"&&(this.handles="n,e,s,w,se,sw,ne,nw");var d=this.handles.split(",");this.handles={};for(var e=0;e<d.length;e++){var f=a.trim(d[e]),g="ui-resizable-"+f,h=a('<div class="ui-resizable-handle '+g+'"></div>');h.css({zIndex:c.zIndex}),"se"==f&&h.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[f]=".ui-resizable-"+f,this.element.append(h)}}this._renderAxis=function(b){b=b||this.element;for(var c in this.handles){this.handles[c].constructor==String&&(this.handles[c]=a(this.handles[c],this.element).show());if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var d=a(this.handles[c],this.element),e=0;e=/sw|ne|nw|se|n|s/.test(c)?d.outerHeight():d.outerWidth();var f=["padding",/ne|nw|n/.test(c)?"Top":/se|sw|s/.test(c)?"Bottom":/^e$/.test(c)?"Right":"Left"].join("");b.css(f,e),this._proportionallyResize()}if(!a(this.handles[c]).length)continue}},this._renderAxis(this.element),this._handles=a(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){if(!b.resizing){if(this.className)var a=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);b.axis=a&&a[1]?a[1]:"se"}}),c.autoHide&&(this._handles.hide(),a(this.element).addClass("ui-resizable-autohide").hover(function(){if(c.disabled)return;a(this).removeClass("ui-resizable-autohide"),b._handles.show()},function(){if(c.disabled)return;b.resizing||(a(this).addClass("ui-resizable-autohide"),b._handles.hide())})),this._mouseInit()},destroy:function(){this._mouseDestroy();var b=function(b){a(b).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){b(this.element);var c=this.element;c.after(this.originalElement.css({position:c.css("position"),width:c.outerWidth(),height:c.outerHeight(),top:c.css("top"),left:c.css("left")})).remove()}return this.originalElement.css("resize",this.originalResizeStyle),b(this.originalElement),this},_mouseCapture:function(b){var c=!1;for(var d in this.handles)a(this.handles[d])[0]==b.target&&(c=!0);return!this.options.disabled&&c},_mouseStart:function(b){var d=this.options,e=this.element.position(),f=this.element;this.resizing=!0,this.documentScroll={top:a(document).scrollTop(),left:a(document).scrollLeft()},(f.is(".ui-draggable")||/absolute/.test(f.css("position")))&&f.css({position:"absolute",top:e.top,left:e.left}),this._renderProxy();var g=c(this.helper.css("left")),h=c(this.helper.css("top"));d.containment&&(g+=a(d.containment).scrollLeft()||0,h+=a(d.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:g,top:h},this.size=this._helper?{width:f.outerWidth(),height:f.outerHeight()}:{width:f.width(),height:f.height()},this.originalSize=this._helper?{width:f.outerWidth(),height:f.outerHeight()}:{width:f.width(),height:f.height()},this.originalPosition={left:g,top:h},this.sizeDiff={width:f.outerWidth()-f.width(),height:f.outerHeight()-f.height()},this.originalMousePosition={left:b.pageX,top:b.pageY},this.aspectRatio=typeof d.aspectRatio=="number"?d.aspectRatio:this.originalSize.width/this.originalSize.height||1;var i=a(".ui-resizable-"+this.axis).css("cursor");return a("body").css("cursor",i=="auto"?this.axis+"-resize":i),f.addClass("ui-resizable-resizing"),this._propagate("start",b),!0},_mouseDrag:function(b){var c=this.helper,d=this.options,e={},f=this,g=this.originalMousePosition,h=this.axis,i=b.pageX-g.left||0,j=b.pageY-g.top||0,k=this._change[h];if(!k)return!1;var l=k.apply(this,[b,i,j]),m=a.browser.msie&&a.browser.version<7,n=this.sizeDiff;this._updateVirtualBoundaries(b.shiftKey);if(this._aspectRatio||b.shiftKey)l=this._updateRatio(l,b);return l=this._respectSize(l,b),this._propagate("resize",b),c.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"}),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),this._updateCache(l),this._trigger("resize",b,this.ui()),!1},_mouseStop:function(b){this.resizing=!1;var c=this.options,d=this;if(this._helper){var e=this._proportionallyResizeElements,f=e.length&&/textarea/i.test(e[0].nodeName),g=f&&a.ui.hasScroll(e[0],"left")?0:d.sizeDiff.height,h=f?0:d.sizeDiff.width,i={width:d.helper.width()-h,height:d.helper.height()-g},j=parseInt(d.element.css("left"),10)+(d.position.left-d.originalPosition.left)||null,k=parseInt(d.element.css("top"),10)+(d.position.top-d.originalPosition.top)||null;c.animate||this.element.css(a.extend(i,{top:k,left:j})),d.helper.height(d.size.height),d.helper.width(d.size.width),this._helper&&!c.animate&&this._proportionallyResize()}return a("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",b),this._helper&&this.helper.remove(),!1},_updateVirtualBoundaries:function(a){var b=this.options,c,e,f,g,h;h={minWidth:d(b.minWidth)?b.minWidth:0,maxWidth:d(b.maxWidth)?b.maxWidth:Infinity,minHeight:d(b.minHeight)?b.minHeight:0,maxHeight:d(b.maxHeight)?b.maxHeight:Infinity};if(this._aspectRatio||a)c=h.minHeight*this.aspectRatio,f=h.minWidth/this.aspectRatio,e=h.maxHeight*this.aspectRatio,g=h.maxWidth/this.aspectRatio,c>h.minWidth&&(h.minWidth=c),f>h.minHeight&&(h.minHeight=f),e<h.maxWidth&&(h.maxWidth=e),g<h.maxHeight&&(h.maxHeight=g);this._vBoundaries=h},_updateCache:function(a){var b=this.options;this.offset=this.helper.offset(),d(a.left)&&(this.position.left=a.left),d(a.top)&&(this.position.top=a.top),d(a.height)&&(this.size.height=a.height),d(a.width)&&(this.size.width=a.width)},_updateRatio:function(a,b){var c=this.options,e=this.position,f=this.size,g=this.axis;return d(a.height)?a.width=a.height*this.aspectRatio:d(a.width)&&(a.height=a.width/this.aspectRatio),g=="sw"&&(a.left=e.left+(f.width-a.width),a.top=null),g=="nw"&&(a.top=e.top+(f.height-a.height),a.left=e.left+(f.width-a.width)),a},_respectSize:function(a,b){var c=this.helper,e=this._vBoundaries,f=this._aspectRatio||b.shiftKey,g=this.axis,h=d(a.width)&&e.maxWidth&&e.maxWidth<a.width,i=d(a.height)&&e.maxHeight&&e.maxHeight<a.height,j=d(a.width)&&e.minWidth&&e.minWidth>a.width,k=d(a.height)&&e.minHeight&&e.minHeight>a.height;j&&(a.width=e.minWidth),k&&(a.height=e.minHeight),h&&(a.width=e.maxWidth),i&&(a.height=e.maxHeight);var l=this.originalPosition.left+this.originalSize.width,m=this.position.top+this.size.height,n=/sw|nw|w/.test(g),o=/nw|ne|n/.test(g);j&&n&&(a.left=l-e.minWidth),h&&n&&(a.left=l-e.maxWidth),k&&o&&(a.top=m-e.minHeight),i&&o&&(a.top=m-e.maxHeight);var p=!a.width&&!a.height;return p&&!a.left&&a.top?a.top=null:p&&!a.top&&a.left&&(a.left=null),a},_proportionallyResize:function(){var b=this.options;if(!this._proportionallyResizeElements.length)return;var c=this.helper||this.element;for(var d=0;d<this._proportionallyResizeElements.length;d++){var e=this._proportionallyResizeElements[d];if(!this.borderDif){var f=[e.css("borderTopWidth"),e.css("borderRightWidth"),e.css("borderBottomWidth"),e.css("borderLeftWidth")],g=[e.css("paddingTop"),e.css("paddingRight"),e.css("paddingBottom"),e.css("paddingLeft")];this.borderDif=a.map(f,function(a,b){var c=parseInt(a,10)||0,d=parseInt(g[b],10)||0;return c+d})}if(!a.browser.msie||!a(c).is(":hidden")&&!a(c).parents(":hidden").length)e.css({height:c.height()-this.borderDif[0]-this.borderDif[2]||0,width:c.width()-this.borderDif[1]-this.borderDif[3]||0});else continue}},_renderProxy:function(){var b=this.element,c=this.options;this.elementOffset=b.offset();if(this._helper){this.helper=this.helper||a('<div style="overflow:hidden;"></div>');var d=a.browser.msie&&a.browser.version<7,e=d?1:0,f=d?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+f,height:this.element.outerHeight()+f,position:"absolute",left:this.elementOffset.left-e+"px",top:this.elementOffset.top-e+"px",zIndex:++c.zIndex}),this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(a,b,c){return{width:this.originalSize.width+b}},w:function(a,b,c){var d=this.options,e=this.originalSize,f=this.originalPosition;return{left:f.left+b,width:e.width-b}},n:function(a,b,c){var d=this.options,e=this.originalSize,f=this.originalPosition;return{top:f.top+c,height:e.height-c}},s:function(a,b,c){return{height:this.originalSize.height+c}},se:function(b,c,d){return a.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[b,c,d]))},sw:function(b,c,d){return a.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[b,c,d]))},ne:function(b,c,d){return a.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[b,c,d]))},nw:function(b,c,d){return a.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[b,c,d]))}},_propagate:function(b,c){a.ui.plugin.call(this,b,[c,this.ui()]),b!="resize"&&this._trigger(b,c,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),a.extend(a.ui.resizable,{version:"1.8.21"}),a.ui.plugin.add("resizable","alsoResize",{start:function(b,c){var d=a(this).data("resizable"),e=d.options,f=function(b){a(b).each(function(){var b=a(this);b.data("resizable-alsoresize",{width:parseInt(b.width(),10),height:parseInt(b.height(),10),left:parseInt(b.css("left"),10),top:parseInt(b.css("top"),10)})})};typeof e.alsoResize=="object"&&!e.alsoResize.parentNode?e.alsoResize.length?(e.alsoResize=e.alsoResize[0],f(e.alsoResize)):a.each(e.alsoResize,function(a){f(a)}):f(e.alsoResize)},resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.originalSize,g=d.originalPosition,h={height:d.size.height-f.height||0,width:d.size.width-f.width||0,top:d.position.top-g.top||0,left:d.position.left-g.left||0},i=function(b,d){a(b).each(function(){var b=a(this),e=a(this).data("resizable-alsoresize"),f={},g=d&&d.length?d:b.parents(c.originalElement[0]).length?["width","height"]:["width","height","top","left"];a.each(g,function(a,b){var c=(e[b]||0)+(h[b]||0);c&&c>=0&&(f[b]=c||null)}),b.css(f)})};typeof e.alsoResize=="object"&&!e.alsoResize.nodeType?a.each(e.alsoResize,function(a,b){i(a,b)}):i(e.alsoResize)},stop:function(b,c){a(this).removeData("resizable-alsoresize")}}),a.ui.plugin.add("resizable","animate",{stop:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d._proportionallyResizeElements,g=f.length&&/textarea/i.test(f[0].nodeName),h=g&&a.ui.hasScroll(f[0],"left")?0:d.sizeDiff.height,i=g?0:d.sizeDiff.width,j={width:d.size.width-i,height:d.size.height-h},k=parseInt(d.element.css("left"),10)+(d.position.left-d.originalPosition.left)||null,l=parseInt(d.element.css("top"),10)+(d.position.top-d.originalPosition.top)||null;d.element.animate(a.extend(j,l&&k?{top:l,left:k}:{}),{duration:e.animateDuration,easing:e.animateEasing,step:function(){var c={width:parseInt(d.element.css("width"),10),height:parseInt(d.element.css("height"),10),top:parseInt(d.element.css("top"),10),left:parseInt(d.element.css("left"),10)};f&&f.length&&a(f[0]).css({width:c.width,height:c.height}),d._updateCache(c),d._propagate("resize",b)}})}}),a.ui.plugin.add("resizable","containment",{start:function(b,d){var e=a(this).data("resizable"),f=e.options,g=e.element,h=f.containment,i=h instanceof a?h.get(0):/parent/.test(h)?g.parent().get(0):h;if(!i)return;e.containerElement=a(i);if(/document/.test(h)||h==document)e.containerOffset={left:0,top:0},e.containerPosition={left:0,top:0},e.parentData={element:a(document),left:0,top:0,width:a(document).width(),height:a(document).height()||document.body.parentNode.scrollHeight};else{var j=a(i),k=[];a(["Top","Right","Left","Bottom"]).each(function(a,b){k[a]=c(j.css("padding"+b))}),e.containerOffset=j.offset(),e.containerPosition=j.position(),e.containerSize={height:j.innerHeight()-k[3],width:j.innerWidth()-k[1]};var l=e.containerOffset,m=e.containerSize.height,n=e.containerSize.width,o=a.ui.hasScroll(i,"left")?i.scrollWidth:n,p=a.ui.hasScroll(i)?i.scrollHeight:m;e.parentData={element:i,left:l.left,top:l.top,width:o,height:p}}},resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.containerSize,g=d.containerOffset,h=d.size,i=d.position,j=d._aspectRatio||b.shiftKey,k={top:0,left:0},l=d.containerElement;l[0]!=document&&/static/.test(l.css("position"))&&(k=g),i.left<(d._helper?g.left:0)&&(d.size.width=d.size.width+(d._helper?d.position.left-g.left:d.position.left-k.left),j&&(d.size.height=d.size.width/d.aspectRatio),d.position.left=e.helper?g.left:0),i.top<(d._helper?g.top:0)&&(d.size.height=d.size.height+(d._helper?d.position.top-g.top:d.position.top),j&&(d.size.width=d.size.height*d.aspectRatio),d.position.top=d._helper?g.top:0),d.offset.left=d.parentData.left+d.position.left,d.offset.top=d.parentData.top+d.position.top;var m=Math.abs((d._helper?d.offset.left-k.left:d.offset.left-k.left)+d.sizeDiff.width),n=Math.abs((d._helper?d.offset.top-k.top:d.offset.top-g.top)+d.sizeDiff.height),o=d.containerElement.get(0)==d.element.parent().get(0),p=/relative|absolute/.test(d.containerElement.css("position"));o&&p&&(m-=d.parentData.left),m+d.size.width>=d.parentData.width&&(d.size.width=d.parentData.width-m,j&&(d.size.height=d.size.width/d.aspectRatio)),n+d.size.height>=d.parentData.height&&(d.size.height=d.parentData.height-n,j&&(d.size.width=d.size.height*d.aspectRatio))},stop:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.position,g=d.containerOffset,h=d.containerPosition,i=d.containerElement,j=a(d.helper),k=j.offset(),l=j.outerWidth()-d.sizeDiff.width,m=j.outerHeight()-d.sizeDiff.height;d._helper&&!e.animate&&/relative/.test(i.css("position"))&&a(this).css({left:k.left-h.left-g.left,width:l,height:m}),d._helper&&!e.animate&&/static/.test(i.css("position"))&&a(this).css({left:k.left-h.left-g.left,width:l,height:m})}}),a.ui.plugin.add("resizable","ghost",{start:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.size;d.ghost=d.originalElement.clone(),d.ghost.css({opacity:.25,display:"block",position:"relative",height:f.height,width:f.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof e.ghost=="string"?e.ghost:""),d.ghost.appendTo(d.helper)},resize:function(b,c){var d=a(this).data("resizable"),e=d.options;d.ghost&&d.ghost.css({position:"relative",height:d.size.height,width:d.size.width})},stop:function(b,c){var d=a(this).data("resizable"),e=d.options;d.ghost&&d.helper&&d.helper.get(0).removeChild(d.ghost.get(0))}}),a.ui.plugin.add("resizable","grid",{resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.size,g=d.originalSize,h=d.originalPosition,i=d.axis,j=e._aspectRatio||b.shiftKey;e.grid=typeof e.grid=="number"?[e.grid,e.grid]:e.grid;var k=Math.round((f.width-g.width)/(e.grid[0]||1))*(e.grid[0]||1),l=Math.round((f.height-g.height)/(e.grid[1]||1))*(e.grid[1]||1);/^(se|s|e)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l):/^(ne)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l,d.position.top=h.top-l):/^(sw)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l,d.position.left=h.left-k):(d.size.width=g.width+k,d.size.height=g.height+l,d.position.top=h.top-l,d.position.left=h.left-k)}});var c=function(a){return parseInt(a,10)||0},d=function(a){return!isNaN(parseInt(a,10))}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.selectable.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.widget("ui.selectable",a.ui.mouse,{options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch"},_create:function(){var b=this;this.element.addClass("ui-selectable"),this.dragged=!1;var c;this.refresh=function(){c=a(b.options.filter,b.element[0]),c.addClass("ui-selectee"),c.each(function(){var b=a(this),c=b.offset();a.data(this,"selectable-item",{element:this,$element:b,left:c.left,top:c.top,right:c.left+b.outerWidth(),bottom:c.top+b.outerHeight(),startselected:!1,selected:b.hasClass("ui-selected"),selecting:b.hasClass("ui-selecting"),unselecting:b.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=c.addClass("ui-selectee"),this._mouseInit(),this.helper=a("<div class='ui-selectable-helper'></div>")},destroy:function(){return this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable"),this._mouseDestroy(),this},_mouseStart:function(b){var c=this;this.opos=[b.pageX,b.pageY];if(this.options.disabled)return;var d=this.options;this.selectees=a(d.filter,this.element[0]),this._trigger("start",b),a(d.appendTo).append(this.helper),this.helper.css({left:b.clientX,top:b.clientY,width:0,height:0}),d.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var d=a.data(this,"selectable-item");d.startselected=!0,!b.metaKey&&!b.ctrlKey&&(d.$element.removeClass("ui-selected"),d.selected=!1,d.$element.addClass("ui-unselecting"),d.unselecting=!0,c._trigger("unselecting",b,{unselecting:d.element}))}),a(b.target).parents().andSelf().each(function(){var d=a.data(this,"selectable-item");if(d){var e=!b.metaKey&&!b.ctrlKey||!d.$element.hasClass("ui-selected");return d.$element.removeClass(e?"ui-unselecting":"ui-selected").addClass(e?"ui-selecting":"ui-unselecting"),d.unselecting=!e,d.selecting=e,d.selected=e,e?c._trigger("selecting",b,{selecting:d.element}):c._trigger("unselecting",b,{unselecting:d.element}),!1}})},_mouseDrag:function(b){var c=this;this.dragged=!0;if(this.options.disabled)return;var d=this.options,e=this.opos[0],f=this.opos[1],g=b.pageX,h=b.pageY;if(e>g){var i=g;g=e,e=i}if(f>h){var i=h;h=f,f=i}return this.helper.css({left:e,top:f,width:g-e,height:h-f}),this.selectees.each(function(){var i=a.data(this,"selectable-item");if(!i||i.element==c.element[0])return;var j=!1;d.tolerance=="touch"?j=!(i.left>g||i.right<e||i.top>h||i.bottom<f):d.tolerance=="fit"&&(j=i.left>e&&i.right<g&&i.top>f&&i.bottom<h),j?(i.selected&&(i.$element.removeClass("ui-selected"),i.selected=!1),i.unselecting&&(i.$element.removeClass("ui-unselecting"),i.unselecting=!1),i.selecting||(i.$element.addClass("ui-selecting"),i.selecting=!0,c._trigger("selecting",b,{selecting:i.element}))):(i.selecting&&((b.metaKey||b.ctrlKey)&&i.startselected?(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.$element.addClass("ui-selected"),i.selected=!0):(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.startselected&&(i.$element.addClass("ui-unselecting"),i.unselecting=!0),c._trigger("unselecting",b,{unselecting:i.element}))),i.selected&&!b.metaKey&&!b.ctrlKey&&!i.startselected&&(i.$element.removeClass("ui-selected"),i.selected=!1,i.$element.addClass("ui-unselecting"),i.unselecting=!0,c._trigger("unselecting",b,{unselecting:i.element})))}),!1},_mouseStop:function(b){var c=this;this.dragged=!1;var d=this.options;return a(".ui-unselecting",this.element[0]).each(function(){var d=a.data(this,"selectable-item");d.$element.removeClass("ui-unselecting"),d.unselecting=!1,d.startselected=!1,c._trigger("unselected",b,{unselected:d.element})}),a(".ui-selecting",this.element[0]).each(function(){var d=a.data(this,"selectable-item");d.$element.removeClass("ui-selecting").addClass("ui-selected"),d.selecting=!1,d.selected=!0,d.startselected=!0,c._trigger("selected",b,{selected:d.element})}),this._trigger("stop",b),this.helper.remove(),!1}}),a.extend(a.ui.selectable,{version:"1.8.21"})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.sortable.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.widget("ui.sortable",a.ui.mouse,{widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3},_create:function(){var a=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?a.axis==="x"||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},destroy:function(){a.Widget.prototype.destroy.call(this),this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var b=this.items.length-1;b>=0;b--)this.items[b].item.removeData(this.widgetName+"-item");return this},_setOption:function(b,c){b==="disabled"?(this.options[b]=c,this.widget()[c?"addClass":"removeClass"]("ui-sortable-disabled")):a.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(b,c){var d=this;if(this.reverting)return!1;if(this.options.disabled||this.options.type=="static")return!1;this._refreshItems(b);var e=null,f=this,g=a(b.target).parents().each(function(){if(a.data(this,d.widgetName+"-item")==f)return e=a(this),!1});a.data(b.target,d.widgetName+"-item")==f&&(e=a(b.target));if(!e)return!1;if(this.options.handle&&!c){var h=!1;a(this.options.handle,e).find("*").andSelf().each(function(){this==b.target&&(h=!0)});if(!h)return!1}return this.currentItem=e,this._removeCurrentsFromItems(),!0},_mouseStart:function(b,c,d){var e=this.options,f=this;this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(b),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(b),this.originalPageX=b.pageX,this.originalPageY=b.pageY,e.cursorAt&&this._adjustOffsetFromHelper(e.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!=this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),e.containment&&this._setContainment(),e.cursor&&(a("body").css("cursor")&&(this._storedCursor=a("body").css("cursor")),a("body").css("cursor",e.cursor)),e.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",e.opacity)),e.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",e.zIndex)),this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",b,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions();if(!d)for(var g=this.containers.length-1;g>=0;g--)this.containers[g]._trigger("activate",b,f._uiHash(this));return a.ui.ddmanager&&(a.ui.ddmanager.current=this),a.ui.ddmanager&&!e.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(b),!0},_mouseDrag:function(b){this.position=this._generatePosition(b),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs);if(this.options.scroll){var c=this.options,d=!1;this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-b.pageY<c.scrollSensitivity?this.scrollParent[0].scrollTop=d=this.scrollParent[0].scrollTop+c.scrollSpeed:b.pageY-this.overflowOffset.top<c.scrollSensitivity&&(this.scrollParent[0].scrollTop=d=this.scrollParent[0].scrollTop-c.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-b.pageX<c.scrollSensitivity?this.scrollParent[0].scrollLeft=d=this.scrollParent[0].scrollLeft+c.scrollSpeed:b.pageX-this.overflowOffset.left<c.scrollSensitivity&&(this.scrollParent[0].scrollLeft=d=this.scrollParent[0].scrollLeft-c.scrollSpeed)):(b.pageY-a(document).scrollTop()<c.scrollSensitivity?d=a(document).scrollTop(a(document).scrollTop()-c.scrollSpeed):a(window).height()-(b.pageY-a(document).scrollTop())<c.scrollSensitivity&&(d=a(document).scrollTop(a(document).scrollTop()+c.scrollSpeed)),b.pageX-a(document).scrollLeft()<c.scrollSensitivity?d=a(document).scrollLeft(a(document).scrollLeft()-c.scrollSpeed):a(window).width()-(b.pageX-a(document).scrollLeft())<c.scrollSensitivity&&(d=a(document).scrollLeft(a(document).scrollLeft()+c.scrollSpeed))),d!==!1&&a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b)}this.positionAbs=this._convertPositionTo("absolute");if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";for(var e=this.items.length-1;e>=0;e--){var f=this.items[e],g=f.item[0],h=this._intersectsWithPointer(f);if(!h)continue;if(g!=this.currentItem[0]&&this.placeholder[h==1?"next":"prev"]()[0]!=g&&!a.ui.contains(this.placeholder[0],g)&&(this.options.type=="semi-dynamic"?!a.ui.contains(this.element[0],g):!0)){this.direction=h==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(f))this._rearrange(b,f);else break;this._trigger("change",b,this._uiHash());break}}return this._contactContainers(b),a.ui.ddmanager&&a.ui.ddmanager.drag(this,b),this._trigger("sort",b,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(b,c){if(!b)return;a.ui.ddmanager&&!this.options.dropBehaviour&&a.ui.ddmanager.drop(this,b);if(this.options.revert){var d=this,e=d.placeholder.offset();d.reverting=!0,a(this.helper).animate({left:e.left-this.offset.parent.left-d.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:e.top-this.offset.parent.top-d.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){d._clear(b)})}else this._clear(b,c);return!1},cancel:function(){var b=this;if(this.dragging){this._mouseUp({target:null}),this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var c=this.containers.length-1;c>=0;c--)this.containers[c]._trigger("deactivate",null,b._uiHash(this)),this.containers[c].containerCache.over&&(this.containers[c]._trigger("out",null,b._uiHash(this)),this.containers[c].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),a.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?a(this.domPosition.prev).after(this.currentItem):a(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(b){var c=this._getItemsAsjQuery(b&&b.connected),d=[];return b=b||{},a(c).each(function(){var c=(a(b.item||this).attr(b.attribute||"id")||"").match(b.expression||/(.+)[-=_](.+)/);c&&d.push((b.key||c[1]+"[]")+"="+(b.key&&b.expression?c[1]:c[2]))}),!d.length&&b.key&&d.push(b.key+"="),d.join("&")},toArray:function(b){var c=this._getItemsAsjQuery(b&&b.connected),d=[];return b=b||{},c.each(function(){d.push(a(b.item||this).attr(b.attribute||"id")||"")}),d},_intersectsWith:function(a){var b=this.positionAbs.left,c=b+this.helperProportions.width,d=this.positionAbs.top,e=d+this.helperProportions.height,f=a.left,g=f+a.width,h=a.top,i=h+a.height,j=this.offset.click.top,k=this.offset.click.left,l=d+j>h&&d+j<i&&b+k>f&&b+k<g;return this.options.tolerance=="pointer"||this.options.forcePointerForContainers||this.options.tolerance!="pointer"&&this.helperProportions[this.floating?"width":"height"]>a[this.floating?"width":"height"]?l:f<b+this.helperProportions.width/2&&c-this.helperProportions.width/2<g&&h<d+this.helperProportions.height/2&&e-this.helperProportions.height/2<i},_intersectsWithPointer:function(b){var c=this.options.axis==="x"||a.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,b.top,b.height),d=this.options.axis==="y"||a.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,b.left,b.width),e=c&&d,f=this._getDragVerticalDirection(),g=this._getDragHorizontalDirection();return e?this.floating?g&&g=="right"||f=="down"?2:1:f&&(f=="down"?2:1):!1},_intersectsWithSides:function(b){var c=a.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,b.top+b.height/2,b.height),d=a.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,b.left+b.width/2,b.width),e=this._getDragVerticalDirection(),f=this._getDragHorizontalDirection();return this.floating&&f?f=="right"&&d||f=="left"&&!d:e&&(e=="down"&&c||e=="up"&&!c)},_getDragVerticalDirection:function(){var a=this.positionAbs.top-this.lastPositionAbs.top;return a!=0&&(a>0?"down":"up")},_getDragHorizontalDirection:function(){var a=this.positionAbs.left-this.lastPositionAbs.left;return a!=0&&(a>0?"right":"left")},refresh:function(a){return this._refreshItems(a),this.refreshPositions(),this},_connectWith:function(){var a=this.options;return a.connectWith.constructor==String?[a.connectWith]:a.connectWith},_getItemsAsjQuery:function(b){var c=this,d=[],e=[],f=this._connectWith();if(f&&b)for(var g=f.length-1;g>=0;g--){var h=a(f[g]);for(var i=h.length-1;i>=0;i--){var j=a.data(h[i],this.widgetName);j&&j!=this&&!j.options.disabled&&e.push([a.isFunction(j.options.items)?j.options.items.call(j.element):a(j.options.items,j.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),j])}}e.push([a.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):a(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(var g=e.length-1;g>=0;g--)e[g][0].each(function(){d.push(this)});return a(d)},_removeCurrentsFromItems:function(){var a=this.currentItem.find(":data("+this.widgetName+"-item)");for(var b=0;b<this.items.length;b++)for(var c=0;c<a.length;c++)a[c]==this.items[b].item[0]&&this.items.splice(b,1)},_refreshItems:function(b){this.items=[],this.containers=[this];var c=this.items,d=this,e=[[a.isFunction(this.options.items)?this.options.items.call(this.element[0],b,{item:this.currentItem}):a(this.options.items,this.element),this]],f=this._connectWith();if(f&&this.ready)for(var g=f.length-1;g>=0;g--){var h=a(f[g]);for(var i=h.length-1;i>=0;i--){var j=a.data(h[i],this.widgetName);j&&j!=this&&!j.options.disabled&&(e.push([a.isFunction(j.options.items)?j.options.items.call(j.element[0],b,{item:this.currentItem}):a(j.options.items,j.element),j]),this.containers.push(j))}}for(var g=e.length-1;g>=0;g--){var k=e[g][1],l=e[g][0];for(var i=0,m=l.length;i<m;i++){var n=a(l[i]);n.data(this.widgetName+"-item",k),c.push({item:n,instance:k,width:0,height:0,left:0,top:0})}}},refreshPositions:function(b){this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());for(var c=this.items.length-1;c>=0;c--){var d=this.items[c];if(d.instance!=this.currentContainer&&this.currentContainer&&d.item[0]!=this.currentItem[0])continue;var e=this.options.toleranceElement?a(this.options.toleranceElement,d.item):d.item;b||(d.width=e.outerWidth(),d.height=e.outerHeight());var f=e.offset();d.left=f.left,d.top=f.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(var c=this.containers.length-1;c>=0;c--){var f=this.containers[c].element.offset();this.containers[c].containerCache.left=f.left,this.containers[c].containerCache.top=f.top,this.containers[c].containerCache.width=this.containers[c].element.outerWidth(),this.containers[c].containerCache.height=this.containers[c].element.outerHeight()}return this},_createPlaceholder:function(b){var c=b||this,d=c.options;if(!d.placeholder||d.placeholder.constructor==String){var e=d.placeholder;d.placeholder={element:function(){var b=a(document.createElement(c.currentItem[0].nodeName)).addClass(e||c.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];return e||(b.style.visibility="hidden"),b},update:function(a,b){if(e&&!d.forcePlaceholderSize)return;b.height()||b.height(c.currentItem.innerHeight()-parseInt(c.currentItem.css("paddingTop")||0,10)-parseInt(c.currentItem.css("paddingBottom")||0,10)),b.width()||b.width(c.currentItem.innerWidth()-parseInt(c.currentItem.css("paddingLeft")||0,10)-parseInt(c.currentItem.css("paddingRight")||0,10))}}}c.placeholder=a(d.placeholder.element.call(c.element,c.currentItem)),c.currentItem.after(c.placeholder),d.placeholder.update(c,c.placeholder)},_contactContainers:function(b){var c=null,d=null;for(var e=this.containers.length-1;e>=0;e--){if(a.ui.contains(this.currentItem[0],this.containers[e].element[0]))continue;if(this._intersectsWith(this.containers[e].containerCache)){if(c&&a.ui.contains(this.containers[e].element[0],c.element[0]))continue;c=this.containers[e],d=e}else this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",b,this._uiHash(this)),this.containers[e].containerCache.over=0)}if(!c)return;if(this.containers.length===1)this.containers[d]._trigger("over",b,this._uiHash(this)),this.containers[d].containerCache.over=1;else if(this.currentContainer!=this.containers[d]){var f=1e4,g=null,h=this.positionAbs[this.containers[d].floating?"left":"top"];for(var i=this.items.length-1;i>=0;i--){if(!a.ui.contains(this.containers[d].element[0],this.items[i].item[0]))continue;var j=this.containers[d].floating?this.items[i].item.offset().left:this.items[i].item.offset().top;Math.abs(j-h)<f&&(f=Math.abs(j-h),g=this.items[i],this.direction=j-h>0?"down":"up")}if(!g&&!this.options.dropOnEmpty)return;this.currentContainer=this.containers[d],g?this._rearrange(b,g,null,!0):this._rearrange(b,null,this.containers[d].element,!0),this._trigger("change",b,this._uiHash()),this.containers[d]._trigger("change",b,this._uiHash(this)),this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[d]._trigger("over",b,this._uiHash(this)),this.containers[d].containerCache.over=1}},_createHelper:function(b){var c=this.options,d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[b,this.currentItem])):c.helper=="clone"?this.currentItem.clone():this.currentItem;return d.parents("body").length||a(c.appendTo!="parent"?c.appendTo:this.currentItem[0].parentNode)[0].appendChild(d[0]),d[0]==this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(d[0].style.width==""||c.forceHelperSize)&&d.width(this.currentItem.width()),(d[0].style.height==""||c.forceHelperSize)&&d.height(this.currentItem.height()),d},_adjustOffsetFromHelper:function(b){typeof b=="string"&&(b=b.split(" ")),a.isArray(b)&&(b={left:+b[0],top:+b[1]||0}),"left"in b&&(this.offset.click.left=b.left+this.margins.left),"right"in b&&(this.offset.click.left=this.helperProportions.width-b.right+this.margins.left),"top"in b&&(this.offset.click.top=b.top+this.margins.top),"bottom"in b&&(this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])&&(b.left+=this.scrollParent.scrollLeft(),b.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)b={top:0,left:0};return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.currentItem.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var b=this.options;b.containment=="parent"&&(b.containment=this.helper[0].parentNode);if(b.containment=="document"||b.containment=="window")this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,a(b.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a(b.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(b.containment)){var c=a(b.containment)[0],d=a(b.containment).offset(),e=a(c).css("overflow")!="hidden";this.containment=[d.left+(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0)-this.margins.left,d.top+(parseInt(a(c).css("borderTopWidth"),10)||0)+(parseInt(a(c).css("paddingTop"),10)||0)-this.margins.top,d.left+(e?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,d.top+(e?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}},_convertPositionTo:function(b,c){c||(c=this.position);var d=b=="absolute"?1:-1,e=this.options,f=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())*d)}},_generatePosition:function(b){var c=this.options,d=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(d[0].tagName);this.cssPosition=="relative"&&(this.scrollParent[0]==document||this.scrollParent[0]==this.offsetParent[0])&&(this.offset.relative=this._getRelativeOffset());var f=b.pageX,g=b.pageY;if(this.originalPosition){this.containment&&(b.pageX-this.offset.click.left<this.containment[0]&&(f=this.containment[0]+this.offset.click.left),b.pageY-this.offset.click.top<this.containment[1]&&(g=this.containment[1]+this.offset.click.top),b.pageX-this.offset.click.left>this.containment[2]&&(f=this.containment[2]+this.offset.click.left),b.pageY-this.offset.click.top>this.containment[3]&&(g=this.containment[3]+this.offset.click.top));if(c.grid){var h=this.originalPageY+Math.round((g-this.originalPageY)/c.grid[1])*c.grid[1];g=this.containment?h-this.offset.click.top<this.containment[1]||h-this.offset.click.top>this.containment[3]?h-this.offset.click.top<this.containment[1]?h+c.grid[1]:h-c.grid[1]:h:h;var i=this.originalPageX+Math.round((f-this.originalPageX)/c.grid[0])*c.grid[0];f=this.containment?i-this.offset.click.left<this.containment[0]||i-this.offset.click.left>this.containment[2]?i-this.offset.click.left<this.containment[0]?i+c.grid[0]:i-c.grid[0]:i:i}}return{top:g-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(a.browser.safari&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():e?0:d.scrollTop()),left:f-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(a.browser.safari&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():e?0:d.scrollLeft())}},_rearrange:function(a,b,c,d){c?c[0].appendChild(this.placeholder[0]):b.item[0].parentNode.insertBefore(this.placeholder[0],this.direction=="down"?b.item[0]:b.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var e=this,f=this.counter;window.setTimeout(function(){f==e.counter&&e.refreshPositions(!d)},0)},_clear:function(b,c){this.reverting=!1;var d=[],e=this;!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null;if(this.helper[0]==this.currentItem[0]){for(var f in this._storedCSS)if(this._storedCSS[f]=="auto"||this._storedCSS[f]=="static")this._storedCSS[f]="";this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();this.fromOutside&&!c&&d.push(function(a){this._trigger("receive",a,this._uiHash(this.fromOutside))}),(this.fromOutside||this.domPosition.prev!=this.currentItem.prev().not(".ui-sortable-helper")[0]||this.domPosition.parent!=this.currentItem.parent()[0])&&!c&&d.push(function(a){this._trigger("update",a,this._uiHash())});if(!a.ui.contains(this.element[0],this.currentItem[0])){c||d.push(function(a){this._trigger("remove",a,this._uiHash())});for(var f=this.containers.length-1;f>=0;f--)a.ui.contains(this.containers[f].element[0],this.currentItem[0])&&!c&&(d.push(function(a){return function(b){a._trigger("receive",b,this._uiHash(this))}}.call(this,this.containers[f])),d.push(function(a){return function(b){a._trigger("update",b,this._uiHash(this))}}.call(this,this.containers[f])))}for(var f=this.containers.length-1;f>=0;f--)c||d.push(function(a){return function(b){a._trigger("deactivate",b,this._uiHash(this))}}.call(this,this.containers[f])),this.containers[f].containerCache.over&&(d.push(function(a){return function(b){a._trigger("out",b,this._uiHash(this))}}.call(this,this.containers[f])),this.containers[f].containerCache.over=0);this._storedCursor&&a("body").css("cursor",this._storedCursor),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex),this.dragging=!1;if(this.cancelHelperRemoval){if(!c){this._trigger("beforeStop",b,this._uiHash());for(var f=0;f<d.length;f++)d[f].call(this,b);this._trigger("stop",b,this._uiHash())}return!1}c||this._trigger("beforeStop",b,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.helper[0]!=this.currentItem[0]&&this.helper.remove(),this.helper=null;if(!c){for(var f=0;f<d.length;f++)d[f].call(this,b);this._trigger("stop",b,this._uiHash())}return this.fromOutside=!1,!0},_trigger:function(){a.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(b){var c=b||this;return{helper:c.helper,placeholder:c.placeholder||a([]),position:c.position,originalPosition:c.originalPosition,offset:c.positionAbs,item:c.currentItem,sender:b?b.element:null}}}),a.extend(a.ui.sortable,{version:"1.8.21"})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.accordion.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.widget("ui.accordion",{options:{active:0,animated:"slide",autoHeight:!0,clearStyle:!1,collapsible:!1,event:"click",fillSpace:!1,header:"> li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:!1,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var b=this,c=b.options;b.running=0,b.element.addClass("ui-accordion ui-widget ui-helper-reset").children("li").addClass("ui-accordion-li-fix"),b.headers=b.element.find(c.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){if(c.disabled)return;a(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){if(c.disabled)return;a(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){if(c.disabled)return;a(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){if(c.disabled)return;a(this).removeClass("ui-state-focus")}),b.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom");if(c.navigation){var d=b.element.find("a").filter(c.navigationFilter).eq(0);if(d.length){var e=d.closest(".ui-accordion-header");e.length?b.active=e:b.active=d.closest(".ui-accordion-content").prev()}}b.active=b._findActive(b.active||c.active).addClass("ui-state-default ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top"),b.active.next().addClass("ui-accordion-content-active"),b._createIcons(),b.resize(),b.element.attr("role","tablist"),b.headers.attr("role","tab").bind("keydown.accordion",function(a){return b._keydown(a)}).next().attr("role","tabpanel"),b.headers.not(b.active||"").attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).next().hide(),b.active.length?b.active.attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}):b.headers.eq(0).attr("tabIndex",0),a.browser.safari||b.headers.find("a").attr("tabIndex",-1),c.event&&b.headers.bind(c.event.split(" ").join(".accordion ")+".accordion",function(a){b._clickHandler.call(b,a,this),a.preventDefault()})},_createIcons:function(){var b=this.options;b.icons&&(a("<span></span>").addClass("ui-icon "+b.icons.header).prependTo(this.headers),this.active.children(".ui-icon").toggleClass(b.icons.header).toggleClass(b.icons.headerSelected),this.element.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.children(".ui-icon").remove(),this.element.removeClass("ui-accordion-icons")},destroy:function(){var b=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("tabIndex"),this.headers.find("a").removeAttr("tabIndex"),this._destroyIcons();var c=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled");return(b.autoHeight||b.fillHeight)&&c.css("height",""),a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments),b=="active"&&this.activate(c),b=="icons"&&(this._destroyIcons(),c&&this._createIcons()),b=="disabled"&&this.headers.add(this.headers.next())[c?"addClass":"removeClass"]("ui-accordion-disabled ui-state-disabled")},_keydown:function(b){if(this.options.disabled||b.altKey||b.ctrlKey)return;var c=a.ui.keyCode,d=this.headers.length,e=this.headers.index(b.target),f=!1;switch(b.keyCode){case c.RIGHT:case c.DOWN:f=this.headers[(e+1)%d];break;case c.LEFT:case c.UP:f=this.headers[(e-1+d)%d];break;case c.SPACE:case c.ENTER:this._clickHandler({target:b.target},b.target),b.preventDefault()}return f?(a(b.target).attr("tabIndex",-1),a(f).attr("tabIndex",0),f.focus(),!1):!0},resize:function(){var b=this.options,c;if(b.fillSpace){if(a.browser.msie){var d=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}c=this.element.parent().height(),a.browser.msie&&this.element.parent().css("overflow",d),this.headers.each(function(){c-=a(this).outerHeight(!0)}),this.headers.next().each(function(){a(this).height(Math.max(0,c-a(this).innerHeight()+a(this).height()))}).css("overflow","auto")}else b.autoHeight&&(c=0,this.headers.next().each(function(){c=Math.max(c,a(this).height("").height())}).height(c));return this},activate:function(a){this.options.active=a;var b=this._findActive(a)[0];return this._clickHandler({target:b},b),this},_findActive:function(b){return b?typeof b=="number"?this.headers.filter(":eq("+b+")"):this.headers.not(this.headers.not(b)):b===!1?a([]):this.headers.filter(":eq(0)")},_clickHandler:function(b,c){var d=this.options;if(d.disabled)return;if(!b.target){if(!d.collapsible)return;this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header),this.active.next().addClass("ui-accordion-content-active");var e=this.active.next(),f={options:d,newHeader:a([]),oldHeader:d.active,newContent:a([]),oldContent:e},g=this.active=a([]);this._toggle(g,e,f);return}var h=a(b.currentTarget||c),i=h[0]===this.active[0];d.active=d.collapsible&&i?!1:this.headers.index(h);if(this.running||!d.collapsible&&i)return;var j=this.active,g=h.next(),e=this.active.next(),f={options:d,newHeader:i&&d.collapsible?a([]):h,oldHeader:this.active,newContent:i&&d.collapsible?a([]):g,oldContent:e},k=this.headers.index(this.active[0])>this.headers.index(h[0]);this.active=i?a([]):h,this._toggle(g,e,f,i,k),j.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header),i||(h.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").children(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected),h.next().addClass("ui-accordion-content-active"));return},_toggle:function(b,c,d,e,f){var g=this,h=g.options;g.toShow=b,g.toHide=c,g.data=d;var i=function(){if(!g)return;return g._completed.apply(g,arguments)};g._trigger("changestart",null,g.data),g.running=c.size()===0?b.size():c.size();if(h.animated){var j={};h.collapsible&&e?j={toShow:a([]),toHide:c,complete:i,down:f,autoHeight:h.autoHeight||h.fillSpace}:j={toShow:b,toHide:c,complete:i,down:f,autoHeight:h.autoHeight||h.fillSpace},h.proxied||(h.proxied=h.animated),h.proxiedDuration||(h.proxiedDuration=h.duration),h.animated=a.isFunction(h.proxied)?h.proxied(j):h.proxied,h.duration=a.isFunction(h.proxiedDuration)?h.proxiedDuration(j):h.proxiedDuration;var k=a.ui.accordion.animations,l=h.duration,m=h.animated;m&&!k[m]&&!a.easing[m]&&(m="slide"),k[m]||(k[m]=function(a){this.slide(a,{easing:m,duration:l||700})}),k[m](j)}else h.collapsible&&e?b.toggle():(c.hide(),b.show()),i(!0);c.prev().attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).blur(),b.prev().attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}).focus()},_completed:function(a){this.running=a?0:--this.running;if(this.running)return;this.options.clearStyle&&this.toShow.add(this.toHide).css({height:"",overflow:""}),this.toHide.removeClass("ui-accordion-content-active"),this.toHide.length&&(this.toHide.parent()[0].className=this.toHide.parent()[0].className),this._trigger("change",null,this.data)}}),a.extend(a.ui.accordion,{version:"1.8.21",animations:{slide:function(b,c){b=a.extend({easing:"swing",duration:300},b,c);if(!b.toHide.size()){b.toShow.animate({height:"show",paddingTop:"show",paddingBottom:"show"},b);return}if(!b.toShow.size()){b.toHide.animate({height:"hide",paddingTop:"hide",paddingBottom:"hide"},b);return}var d=b.toShow.css("overflow"),e=0,f={},g={},h=["height","paddingTop","paddingBottom"],i,j=b.toShow;i=j[0].style.width,j.width(j.parent().width()-parseFloat(j.css("paddingLeft"))-parseFloat(j.css("paddingRight"))-(parseFloat(j.css("borderLeftWidth"))||0)-(parseFloat(j.css("borderRightWidth"))||0)),a.each(h,function(c,d){g[d]="hide";var e=(""+a.css(b.toShow[0],d)).match(/^([\d+-.]+)(.*)$/);f[d]={value:e[1],unit:e[2]||"px"}}),b.toShow.css({height:0,overflow:"hidden"}).show(),b.toHide.filter(":hidden").each(b.complete).end().filter(":visible").animate(g,{step:function(a,c){c.prop=="height"&&(e=c.end-c.start===0?0:(c.now-c.start)/(c.end-c.start)),b.toShow[0].style[c.prop]=e*f[c.prop].value+f[c.prop].unit},duration:b.duration,easing:b.easing,complete:function(){b.autoHeight||b.toShow.css("height",""),b.toShow.css({width:i,overflow:d}),b.complete()}})},bounceslide:function(a){this.slide(a,{easing:a.down?"easeOutBounce":"swing",duration:a.down?1e3:200})}}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.autocomplete.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){var c=0;a.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var b=this,c=this.element[0].ownerDocument,d;this.isMultiLine=this.element.is("textarea"),this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(b.options.disabled||b.element.propAttr("readOnly"))return;d=!1;var e=a.ui.keyCode;switch(c.keyCode){case e.PAGE_UP:b._move("previousPage",c);break;case e.PAGE_DOWN:b._move("nextPage",c);break;case e.UP:b._keyEvent("previous",c);break;case e.DOWN:b._keyEvent("next",c);break;case e.ENTER:case e.NUMPAD_ENTER:b.menu.active&&(d=!0,c.preventDefault());case e.TAB:if(!b.menu.active)return;b.menu.select(c);break;case e.ESCAPE:b.element.val(b.term),b.close(c);break;default:clearTimeout(b.searching),b.searching=setTimeout(function(){b.term!=b.element.val()&&(b.selectedItem=null,b.search(null,c))},b.options.delay)}}).bind("keypress.autocomplete",function(a){d&&(d=!1,a.preventDefault())}).bind("focus.autocomplete",function(){if(b.options.disabled)return;b.selectedItem=null,b.previous=b.element.val()}).bind("blur.autocomplete",function(a){if(b.options.disabled)return;clearTimeout(b.searching),b.closing=setTimeout(function(){b.close(a),b._change(a)},150)}),this._initSource(),this.menu=a("<ul></ul>").addClass("ui-autocomplete").appendTo(a(this.options.appendTo||"body",c)[0]).mousedown(function(c){var d=b.menu.element[0];a(c.target).closest(".ui-menu-item").length||setTimeout(function(){a(document).one("mousedown",function(c){c.target!==b.element[0]&&c.target!==d&&!a.ui.contains(d,c.target)&&b.close()})},1),setTimeout(function(){clearTimeout(b.closing)},13)}).menu({focus:function(a,c){var d=c.item.data("item.autocomplete");!1!==b._trigger("focus",a,{item:d})&&/^key/.test(a.originalEvent.type)&&b.element.val(d.value)},selected:function(a,d){var e=d.item.data("item.autocomplete"),f=b.previous;b.element[0]!==c.activeElement&&(b.element.focus(),b.previous=f,setTimeout(function(){b.previous=f,b.selectedItem=e},1)),!1!==b._trigger("select",a,{item:e})&&b.element.val(e.value),b.term=b.element.val(),b.close(a),b.selectedItem=e},blur:function(a,c){b.menu.element.is(":visible")&&b.element.val()!==b.term&&b.element.val(b.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu"),a.fn.bgiframe&&this.menu.element.bgiframe(),b.beforeunloadHandler=function(){b.element.removeAttr("autocomplete")},a(window).bind("beforeunload",b.beforeunloadHandler)},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup"),this.menu.element.remove(),a(window).unbind("beforeunload",this.beforeunloadHandler),a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments),b==="source"&&this._initSource(),b==="appendTo"&&this.menu.element.appendTo(a(c||"body",this.element[0].ownerDocument)[0]),b==="disabled"&&c&&this.xhr&&this.xhr.abort()},_initSource:function(){var b=this,c,d;a.isArray(this.options.source)?(c=this.options.source,this.source=function(b,d){d(a.ui.autocomplete.filter(c,b.term))}):typeof this.options.source=="string"?(d=this.options.source,this.source=function(c,e){b.xhr&&b.xhr.abort(),b.xhr=a.ajax({url:d,data:c,dataType:"json",success:function(a,b){e(a)},error:function(){e([])}})}):this.source=this.options.source},search:function(a,b){a=a!=null?a:this.element.val(),this.term=this.element.val();if(a.length<this.options.minLength)return this.close(b);clearTimeout(this.closing);if(this._trigger("search",b)===!1)return;return this._search(a)},_search:function(a){this.pending++,this.element.addClass("ui-autocomplete-loading"),this.source({term:a},this._response())},_response:function(){var a=this,b=++c;return function(d){b===c&&a.__response(d),a.pending--,a.pending||a.element.removeClass("ui-autocomplete-loading")}},__response:function(a){!this.options.disabled&&a&&a.length?(a=this._normalize(a),this._suggest(a),this._trigger("open")):this.close()},close:function(a){clearTimeout(this.closing),this.menu.element.is(":visible")&&(this.menu.element.hide(),this.menu.deactivate(),this._trigger("close",a))},_change:function(a){this.previous!==this.element.val()&&this._trigger("change",a,{item:this.selectedItem})},_normalize:function(b){return b.length&&b[0].label&&b[0].value?b:a.map(b,function(b){return typeof b=="string"?{label:b,value:b}:a.extend({label:b.label||b.value,value:b.value||b.label},b)})},_suggest:function(b){var c=this.menu.element.empty().zIndex(this.element.zIndex()+1);this._renderMenu(c,b),this.menu.deactivate(),this.menu.refresh(),c.show(),this._resizeMenu(),c.position(a.extend({of:this.element},this.options.position)),this.options.autoFocus&&this.menu.next(new a.Event("mouseover"))},_resizeMenu:function(){var a=this.menu.element;a.outerWidth(Math.max(a.width("").outerWidth()+1,this.element.outerWidth()))},_renderMenu:function(b,c){var d=this;a.each(c,function(a,c){d._renderItem(b,c)})},_renderItem:function(b,c){return a("<li></li>").data("item.autocomplete",c).append(a("<a></a>").text(c.label)).appendTo(b)},_move:function(a,b){if(!this.menu.element.is(":visible")){this.search(null,b);return}if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term),this.menu.deactivate();return}this.menu[a](b)},widget:function(){return this.menu.element},_keyEvent:function(a,b){if(!this.isMultiLine||this.menu.element.is(":visible"))this._move(a,b),b.preventDefault()}}),a.extend(a.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&")},filter:function(b,c){var d=new RegExp(a.ui.autocomplete.escapeRegex(c),"i");return a.grep(b,function(a){return d.test(a.label||a.value||a)})}})})(jQuery),function(a){a.widget("ui.menu",{_create:function(){var b=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(c){if(!a(c.target).closest(".ui-menu-item a").length)return;c.preventDefault(),b.select(c)}),this.refresh()},refresh:function(){var b=this,c=this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem");c.children("a").addClass("ui-corner-all").attr("tabindex",-1).mouseenter(function(c){b.activate(c,a(this).parent())}).mouseleave(function(){b.deactivate()})},activate:function(a,b){this.deactivate();if(this.hasScroll()){var c=b.offset().top-this.element.offset().top,d=this.element.scrollTop(),e=this.element.height();c<0?this.element.scrollTop(d+c):c>=e&&this.element.scrollTop(d+c-e+b.height())}this.active=b.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end(),this._trigger("focus",a,{item:b})},deactivate:function(){if(!this.active)return;this.active.children("a").removeClass("ui-state-hover").removeAttr("id"),this._trigger("blur"),this.active=null},next:function(a){this.move("next",".ui-menu-item:first",a)},previous:function(a){this.move("prev",".ui-menu-item:last",a)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(a,b,c){if(!this.active){this.activate(c,this.element.children(b));return}var d=this.active[a+"All"](".ui-menu-item").eq(0);d.length?this.activate(c,d):this.activate(c,this.element.children(b))},nextPage:function(b){if(this.hasScroll()){if(!this.active||this.last()){this.activate(b,this.element.children(".ui-menu-item:first"));return}var c=this.active.offset().top,d=this.element.height(),e=this.element.children(".ui-menu-item").filter(function(){var b=a(this).offset().top-c-d+a(this).height();return b<10&&b>-10});e.length||(e=this.element.children(".ui-menu-item:last")),this.activate(b,e)}else this.activate(b,this.element.children(".ui-menu-item").filter(!this.active||this.last()?":first":":last"))},previousPage:function(b){if(this.hasScroll()){if(!this.active||this.first()){this.activate(b,this.element.children(".ui-menu-item:last"));return}var c=this.active.offset().top,d=this.element.height(),e=this.element.children(".ui-menu-item").filter(function(){var b=a(this).offset().top-c+d-a(this).height();return b<10&&b>-10});e.length||(e=this.element.children(".ui-menu-item:first")),this.activate(b,e)}else this.activate(b,this.element.children(".ui-menu-item").filter(!this.active||this.first()?":last":":first"))},hasScroll:function(){return this.element.height()<this.element[a.fn.prop?"prop":"attr"]("scrollHeight")},select:function(a){this._trigger("selected",a,{item:this.active})}})}(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.button.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){var c,d,e,f,g="ui-button ui-widget ui-state-default ui-corner-all",h="ui-state-hover ui-state-active ",i="ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",j=function(){var b=a(this).find(":ui-button");setTimeout(function(){b.button("refresh")},1)},k=function(b){var c=b.name,d=b.form,e=a([]);return c&&(d?e=a(d).find("[name='"+c+"']"):e=a("[name='"+c+"']",b.ownerDocument).filter(function(){return!this.form})),e};a.widget("ui.button",{options:{disabled:null,text:!0,label:null,icons:{primary:null,secondary:null}},_create:function(){this.element.closest("form").unbind("reset.button").bind("reset.button",j),typeof this.options.disabled!="boolean"?this.options.disabled=!!this.element.propAttr("disabled"):this.element.propAttr("disabled",this.options.disabled),this._determineButtonType(),this.hasTitle=!!this.buttonElement.attr("title");var b=this,h=this.options,i=this.type==="checkbox"||this.type==="radio",l="ui-state-hover"+(i?"":" ui-state-active"),m="ui-state-focus";h.label===null&&(h.label=this.buttonElement.html()),this.buttonElement.addClass(g).attr("role","button").bind("mouseenter.button",function(){if(h.disabled)return;a(this).addClass("ui-state-hover"),this===c&&a(this).addClass("ui-state-active")}).bind("mouseleave.button",function(){if(h.disabled)return;a(this).removeClass(l)}).bind("click.button",function(a){h.disabled&&(a.preventDefault(),a.stopImmediatePropagation())}),this.element.bind("focus.button",function(){b.buttonElement.addClass(m)}).bind("blur.button",function(){b.buttonElement.removeClass(m)}),i&&(this.element.bind("change.button",function(){if(f)return;b.refresh()}),this.buttonElement.bind("mousedown.button",function(a){if(h.disabled)return;f=!1,d=a.pageX,e=a.pageY}).bind("mouseup.button",function(a){if(h.disabled)return;if(d!==a.pageX||e!==a.pageY)f=!0})),this.type==="checkbox"?this.buttonElement.bind("click.button",function(){if(h.disabled||f)return!1;a(this).toggleClass("ui-state-active"),b.buttonElement.attr("aria-pressed",b.element[0].checked)}):this.type==="radio"?this.buttonElement.bind("click.button",function(){if(h.disabled||f)return!1;a(this).addClass("ui-state-active"),b.buttonElement.attr("aria-pressed","true");var c=b.element[0];k(c).not(c).map(function(){return a(this).button("widget")[0]}).removeClass("ui-state-active").attr("aria-pressed","false")}):(this.buttonElement.bind("mousedown.button",function(){if(h.disabled)return!1;a(this).addClass("ui-state-active"),c=this,a(document).one("mouseup",function(){c=null})}).bind("mouseup.button",function(){if(h.disabled)return!1;a(this).removeClass("ui-state-active")}).bind("keydown.button",function(b){if(h.disabled)return!1;(b.keyCode==a.ui.keyCode.SPACE||b.keyCode==a.ui.keyCode.ENTER)&&a(this).addClass("ui-state-active")}).bind("keyup.button",function(){a(this).removeClass("ui-state-active")}),this.buttonElement.is("a")&&this.buttonElement.keyup(function(b){b.keyCode===a.ui.keyCode.SPACE&&a(this).click()})),this._setOption("disabled",h.disabled),this._resetButton()},_determineButtonType:function(){this.element.is(":checkbox")?this.type="checkbox":this.element.is(":radio")?this.type="radio":this.element.is("input")?this.type="input":this.type="button";if(this.type==="checkbox"||this.type==="radio"){var a=this.element.parents().filter(":last"),b="label[for='"+this.element.attr("id")+"']";this.buttonElement=a.find(b),this.buttonElement.length||(a=a.length?a.siblings():this.element.siblings(),this.buttonElement=a.filter(b),this.buttonElement.length||(this.buttonElement=a.find(b))),this.element.addClass("ui-helper-hidden-accessible");var c=this.element.is(":checked");c&&this.buttonElement.addClass("ui-state-active"),this.buttonElement.attr("aria-pressed",c)}else this.buttonElement=this.element},widget:function(){return this.buttonElement},destroy:function(){this.element.removeClass("ui-helper-hidden-accessible"),this.buttonElement.removeClass(g+" "+h+" "+i).removeAttr("role").removeAttr("aria-pressed").html(this.buttonElement.find(".ui-button-text").html()),this.hasTitle||this.buttonElement.removeAttr("title"),a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments);if(b==="disabled"){c?this.element.propAttr("disabled",!0):this.element.propAttr("disabled",!1);return}this._resetButton()},refresh:function(){var b=this.element.is(":disabled");b!==this.options.disabled&&this._setOption("disabled",b),this.type==="radio"?k(this.element[0]).each(function(){a(this).is(":checked")?a(this).button("widget").addClass("ui-state-active").attr("aria-pressed","true"):a(this).button("widget").removeClass("ui-state-active").attr("aria-pressed","false")}):this.type==="checkbox"&&(this.element.is(":checked")?this.buttonElement.addClass("ui-state-active").attr("aria-pressed","true"):this.buttonElement.removeClass("ui-state-active").attr("aria-pressed","false"))},_resetButton:function(){if(this.type==="input"){this.options.label&&this.element.val(this.options.label);return}var b=this.buttonElement.removeClass(i),c=a("<span></span>",this.element[0].ownerDocument).addClass("ui-button-text").html(this.options.label).appendTo(b.empty()).text(),d=this.options.icons,e=d.primary&&d.secondary,f=[];d.primary||d.secondary?(this.options.text&&f.push("ui-button-text-icon"+(e?"s":d.primary?"-primary":"-secondary")),d.primary&&b.prepend("<span class='ui-button-icon-primary ui-icon "+d.primary+"'></span>"),d.secondary&&b.append("<span class='ui-button-icon-secondary ui-icon "+d.secondary+"'></span>"),this.options.text||(f.push(e?"ui-button-icons-only":"ui-button-icon-only"),this.hasTitle||b.attr("title",c))):f.push("ui-button-text-only"),b.addClass(f.join(" "))}}),a.widget("ui.buttonset",{options:{items:":button, :submit, :reset, :checkbox, :radio, a, :data(button)"},_create:function(){this.element.addClass("ui-buttonset")},_init:function(){this.refresh()},_setOption:function(b,c){b==="disabled"&&this.buttons.button("option",b,c),a.Widget.prototype._setOption.apply(this,arguments)},refresh:function(){var b=this.element.css("direction")==="rtl";this.buttons=this.element.find(this.options.items).filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass(b?"ui-corner-right":"ui-corner-left").end().filter(":last").addClass(b?"ui-corner-left":"ui-corner-right").end().end()},destroy:function(){this.element.removeClass("ui-buttonset"),this.buttons.map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy"),a.Widget.prototype.destroy.call(this)}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.dialog.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){var c="ui-dialog ui-widget ui-widget-content ui-corner-all ",d={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},e={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},f=a.attrFn||{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0,click:!0};a.widget("ui.dialog",{options:{autoOpen:!0,buttons:{},closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:!1,maxWidth:!1,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",collision:"fit",using:function(b){var c=a(this).css(b).offset().top;c<0&&a(this).css("top",b.top-c)}},resizable:!0,show:null,stack:!0,title:"",width:300,zIndex:1e3},_create:function(){this.originalTitle=this.element.attr("title"),typeof this.originalTitle!="string"&&(this.originalTitle=""),this.options.title=this.options.title||this.originalTitle;var b=this,d=b.options,e=d.title||"&#160;",f=a.ui.dialog.getTitleId(b.element),g=(b.uiDialog=a("<div></div>")).appendTo(document.body).hide().addClass(c+d.dialogClass).css({zIndex:d.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(c){d.closeOnEscape&&!c.isDefaultPrevented()&&c.keyCode&&c.keyCode===a.ui.keyCode.ESCAPE&&(b.close(c),c.preventDefault())}).attr({role:"dialog","aria-labelledby":f}).mousedown(function(a){b.moveToTop(!1,a)}),h=b.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(g),i=(b.uiDialogTitlebar=a("<div></div>")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(g),j=a('<a href="#"></a>').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){j.addClass("ui-state-hover")},function(){j.removeClass("ui-state-hover")}).focus(function(){j.addClass("ui-state-focus")}).blur(function(){j.removeClass("ui-state-focus")}).click(function(a){return b.close(a),!1}).appendTo(i),k=(b.uiDialogTitlebarCloseText=a("<span></span>")).addClass("ui-icon ui-icon-closethick").text(d.closeText).appendTo(j),l=a("<span></span>").addClass("ui-dialog-title").attr("id",f).html(e).prependTo(i);a.isFunction(d.beforeclose)&&!a.isFunction(d.beforeClose)&&(d.beforeClose=d.beforeclose),i.find("*").add(i).disableSelection(),d.draggable&&a.fn.draggable&&b._makeDraggable(),d.resizable&&a.fn.resizable&&b._makeResizable(),b._createButtons(d.buttons),b._isOpen=!1,a.fn.bgiframe&&g.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var a=this;return a.overlay&&a.overlay.destroy(),a.uiDialog.hide(),a.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"),a.uiDialog.remove(),a.originalTitle&&a.element.attr("title",a.originalTitle),a},widget:function(){return this.uiDialog},close:function(b){var c=this,d,e;if(!1===c._trigger("beforeClose",b))return;return c.overlay&&c.overlay.destroy(),c.uiDialog.unbind("keypress.ui-dialog"),c._isOpen=!1,c.options.hide?c.uiDialog.hide(c.options.hide,function(){c._trigger("close",b)}):(c.uiDialog.hide(),c._trigger("close",b)),a.ui.dialog.overlay.resize(),c.options.modal&&(d=0,a(".ui-dialog").each(function(){this!==c.uiDialog[0]&&(e=a(this).css("z-index"),isNaN(e)||(d=Math.max(d,e)))}),a.ui.dialog.maxZ=d),c},isOpen:function(){return this._isOpen},moveToTop:function(b,c){var d=this,e=d.options,f;return e.modal&&!b||!e.stack&&!e.modal?d._trigger("focus",c):(e.zIndex>a.ui.dialog.maxZ&&(a.ui.dialog.maxZ=e.zIndex),d.overlay&&(a.ui.dialog.maxZ+=1,d.overlay.$el.css("z-index",a.ui.dialog.overlay.maxZ=a.ui.dialog.maxZ)),f={scrollTop:d.element.scrollTop(),scrollLeft:d.element.scrollLeft()},a.ui.dialog.maxZ+=1,d.uiDialog.css("z-index",a.ui.dialog.maxZ),d.element.attr(f),d._trigger("focus",c),d)},open:function(){if(this._isOpen)return;var b=this,c=b.options,d=b.uiDialog;return b.overlay=c.modal?new a.ui.dialog.overlay(b):null,b._size(),b._position(c.position),d.show(c.show),b.moveToTop(!0),c.modal&&d.bind("keydown.ui-dialog",function(b){if(b.keyCode!==a.ui.keyCode.TAB)return;var c=a(":tabbable",this),d=c.filter(":first"),e=c.filter(":last");if(b.target===e[0]&&!b.shiftKey)return d.focus(1),!1;if(b.target===d[0]&&b.shiftKey)return e.focus(1),!1}),a(b.element.find(":tabbable").get().concat(d.find(".ui-dialog-buttonpane :tabbable").get().concat(d.get()))).eq(0).focus(),b._isOpen=!0,b._trigger("open"),b},_createButtons:function(b){var c=this,d=!1,e=a("<div></div>").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),g=a("<div></div>").addClass("ui-dialog-buttonset").appendTo(e);c.uiDialog.find(".ui-dialog-buttonpane").remove(),typeof b=="object"&&b!==null&&a.each(b,function(){return!(d=!0)}),d&&(a.each(b,function(b,d){d=a.isFunction(d)?{click:d,text:b}:d;var e=a('<button type="button"></button>').click(function(){d.click.apply(c.element[0],arguments)}).appendTo(g);a.each(d,function(a,b){if(a==="click")return;a in f?e[a](b):e.attr(a,b)}),a.fn.button&&e.button()}),e.appendTo(c.uiDialog))},_makeDraggable:function(){function f(a){return{position:a.position,offset:a.offset}}var b=this,c=b.options,d=a(document),e;b.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(d,g){e=c.height==="auto"?"auto":a(this).height(),a(this).height(a(this).height()).addClass("ui-dialog-dragging"),b._trigger("dragStart",d,f(g))},drag:function(a,c){b._trigger("drag",a,f(c))},stop:function(g,h){c.position=[h.position.left-d.scrollLeft(),h.position.top-d.scrollTop()],a(this).removeClass("ui-dialog-dragging").height(e),b._trigger("dragStop",g,f(h)),a.ui.dialog.overlay.resize()}})},_makeResizable:function(c){function h(a){return{originalPosition:a.originalPosition,originalSize:a.originalSize,position:a.position,size:a.size}}c=c===b?this.options.resizable:c;var d=this,e=d.options,f=d.uiDialog.css("position"),g=typeof c=="string"?c:"n,e,s,w,se,sw,ne,nw";d.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:d.element,maxWidth:e.maxWidth,maxHeight:e.maxHeight,minWidth:e.minWidth,minHeight:d._minHeight(),handles:g,start:function(b,c){a(this).addClass("ui-dialog-resizing"),d._trigger("resizeStart",b,h(c))},resize:function(a,b){d._trigger("resize",a,h(b))},stop:function(b,c){a(this).removeClass("ui-dialog-resizing"),e.height=a(this).height(),e.width=a(this).width(),d._trigger("resizeStop",b,h(c)),a.ui.dialog.overlay.resize()}}).css("position",f).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var a=this.options;return a.height==="auto"?a.minHeight:Math.min(a.minHeight,a.height)},_position:function(b){var c=[],d=[0,0],e;if(b){if(typeof b=="string"||typeof b=="object"&&"0"in b)c=b.split?b.split(" "):[b[0],b[1]],c.length===1&&(c[1]=c[0]),a.each(["left","top"],function(a,b){+c[a]===c[a]&&(d[a]=c[a],c[a]=b)}),b={my:c.join(" "),at:c.join(" "),offset:d.join(" ")};b=a.extend({},a.ui.dialog.prototype.options.position,b)}else b=a.ui.dialog.prototype.options.position;e=this.uiDialog.is(":visible"),e||this.uiDialog.show(),this.uiDialog.css({top:0,left:0}).position(a.extend({of:window},b)),e||this.uiDialog.hide()},_setOptions:function(b){var c=this,f={},g=!1;a.each(b,function(a,b){c._setOption(a,b),a in d&&(g=!0),a in e&&(f[a]=b)}),g&&this._size(),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",f)},_setOption:function(b,d){var e=this,f=e.uiDialog;switch(b){case"beforeclose":b="beforeClose";break;case"buttons":e._createButtons(d);break;case"closeText":e.uiDialogTitlebarCloseText.text(""+d);break;case"dialogClass":f.removeClass(e.options.dialogClass).addClass(c+d);break;case"disabled":d?f.addClass("ui-dialog-disabled"):f.removeClass("ui-dialog-disabled");break;case"draggable":var g=f.is(":data(draggable)");g&&!d&&f.draggable("destroy"),!g&&d&&e._makeDraggable();break;case"position":e._position(d);break;case"resizable":var h=f.is(":data(resizable)");h&&!d&&f.resizable("destroy"),h&&typeof d=="string"&&f.resizable("option","handles",d),!h&&d!==!1&&e._makeResizable(d);break;case"title":a(".ui-dialog-title",e.uiDialogTitlebar).html(""+(d||"&#160;"))}a.Widget.prototype._setOption.apply(e,arguments)},_size:function(){var b=this.options,c,d,e=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0}),b.minWidth>b.width&&(b.width=b.minWidth),c=this.uiDialog.css({height:"auto",width:b.width}).height(),d=Math.max(0,b.minHeight-c);if(b.height==="auto")if(a.support.minHeight)this.element.css({minHeight:d,height:"auto"});else{this.uiDialog.show();var f=this.element.css("height","auto").height();e||this.uiDialog.hide(),this.element.height(Math.max(f,d))}else this.element.height(Math.max(b.height-c,0));this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}}),a.extend(a.ui.dialog,{version:"1.8.21",uuid:0,maxZ:0,getTitleId:function(a){var b=a.attr("id");return b||(this.uuid+=1,b=this.uuid),"ui-dialog-title-"+b},overlay:function(b){this.$el=a.ui.dialog.overlay.create(b)}}),a.extend(a.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:a.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(a){return a+".dialog-overlay"}).join(" "),create:function(b){this.instances.length===0&&(setTimeout(function(){a.ui.dialog.overlay.instances.length&&a(document).bind(a.ui.dialog.overlay.events,function(b){if(a(b.target).zIndex()<a.ui.dialog.overlay.maxZ)return!1})},1),a(document).bind("keydown.dialog-overlay",function(c){b.options.closeOnEscape&&!c.isDefaultPrevented()&&c.keyCode&&c.keyCode===a.ui.keyCode.ESCAPE&&(b.close(c),c.preventDefault())}),a(window).bind("resize.dialog-overlay",a.ui.dialog.overlay.resize));var c=(this.oldInstances.pop()||a("<div></div>").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(),height:this.height()});return a.fn.bgiframe&&c.bgiframe(),this.instances.push(c),c},destroy:function(b){var c=a.inArray(b,this.instances);c!=-1&&this.oldInstances.push(this.instances.splice(c,1)[0]),this.instances.length===0&&a([document,window]).unbind(".dialog-overlay"),b.remove();var d=0;a.each(this.instances,function(){d=Math.max(d,this.css("z-index"))}),this.maxZ=d},height:function(){var b,c;return a.browser.msie&&a.browser.version<7?(b=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),c=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight),b<c?a(window).height()+"px":b+"px"):a(document).height()+"px"},width:function(){var b,c;return a.browser.msie?(b=Math.max(document.documentElement.scrollWidth,document.body.scrollWidth),c=Math.max(document.documentElement.offsetWidth,document.body.offsetWidth),b<c?a(window).width()+"px":b+"px"):a(document).width()+"px"},resize:function(){var b=a([]);a.each(a.ui.dialog.overlay.instances,function(){b=b.add(this)}),b.css({width:0,height:0}).css({width:a.ui.dialog.overlay.width(),height:a.ui.dialog.overlay.height()})}}),a.extend(a.ui.dialog.overlay.prototype,{destroy:function(){a.ui.dialog.overlay.destroy(this.$el)}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.slider.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){var c=5;a.widget("ui.slider",a.ui.mouse,{widgetEventPrefix:"slide",options:{animate:!1,distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null},_create:function(){var b=this,d=this.options,e=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),f="<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",g=d.values&&d.values.length||1,h=[];this._keySliding=!1,this._mouseSliding=!1,this._animateOff=!0,this._handleIndex=null,this._detectOrientation(),this._mouseInit(),this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget"+" ui-widget-content"+" ui-corner-all"+(d.disabled?" ui-slider-disabled ui-disabled":"")),this.range=a([]),d.range&&(d.range===!0&&(d.values||(d.values=[this._valueMin(),this._valueMin()]),d.values.length&&d.values.length!==2&&(d.values=[d.values[0],d.values[0]])),this.range=a("<div></div>").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+(d.range==="min"||d.range==="max"?" ui-slider-range-"+d.range:"")));for(var i=e.length;i<g;i+=1)h.push(f);this.handles=e.add(a(h.join("")).appendTo(b.element)),this.handle=this.handles.eq(0),this.handles.add(this.range).filter("a").click(function(a){a.preventDefault()}).hover(function(){d.disabled||a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")}).focus(function(){d.disabled?a(this).blur():(a(".ui-slider .ui-state-focus").removeClass("ui-state-focus"),a(this).addClass("ui-state-focus"))}).blur(function(){a(this).removeClass("ui-state-focus")}),this.handles.each(function(b){a(this).data("index.ui-slider-handle",b)}),this.handles.keydown(function(d){var e=a(this).data("index.ui-slider-handle"),f,g,h,i;if(b.options.disabled)return;switch(d.keyCode){case a.ui.keyCode.HOME:case a.ui.keyCode.END:case a.ui.keyCode.PAGE_UP:case a.ui.keyCode.PAGE_DOWN:case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:d.preventDefault();if(!b._keySliding){b._keySliding=!0,a(this).addClass("ui-state-active"),f=b._start(d,e);if(f===!1)return}}i=b.options.step,b.options.values&&b.options.values.length?g=h=b.values(e):g=h=b.value();switch(d.keyCode){case a.ui.keyCode.HOME:h=b._valueMin();break;case a.ui.keyCode.END:h=b._valueMax();break;case a.ui.keyCode.PAGE_UP:h=b._trimAlignValue(g+(b._valueMax()-b._valueMin())/c);break;case a.ui.keyCode.PAGE_DOWN:h=b._trimAlignValue(g-(b._valueMax()-b._valueMin())/c);break;case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:if(g===b._valueMax())return;h=b._trimAlignValue(g+i);break;case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:if(g===b._valueMin())return;h=b._trimAlignValue(g-i)}b._slide(d,e,h)}).keyup(function(c){var d=a(this).data("index.ui-slider-handle");b._keySliding&&(b._keySliding=!1,b._stop(c,d),b._change(c,d),a(this).removeClass("ui-state-active"))}),this._refreshValue(),this._animateOff=!1},destroy:function(){return this.handles.remove(),this.range.remove(),this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider"),this._mouseDestroy(),this},_mouseCapture:function(b){var c=this.options,d,e,f,g,h,i,j,k,l;return c.disabled?!1:(this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()},this.elementOffset=this.element.offset(),d={x:b.pageX,y:b.pageY},e=this._normValueFromMouse(d),f=this._valueMax()-this._valueMin()+1,h=this,this.handles.each(function(b){var c=Math.abs(e-h.values(b));f>c&&(f=c,g=a(this),i=b)}),c.range===!0&&this.values(1)===c.min&&(i+=1,g=a(this.handles[i])),j=this._start(b,i),j===!1?!1:(this._mouseSliding=!0,h._handleIndex=i,g.addClass("ui-state-active").focus(),k=g.offset(),l=!a(b.target).parents().andSelf().is(".ui-slider-handle"),this._clickOffset=l?{left:0,top:0}:{left:b.pageX-k.left-g.width()/2,top:b.pageY-k.top-g.height()/2-(parseInt(g.css("borderTopWidth"),10)||0)-(parseInt(g.css("borderBottomWidth"),10)||0)+(parseInt(g.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(b,i,e),this._animateOff=!0,!0))},_mouseStart:function(a){return!0},_mouseDrag:function(a){var b={x:a.pageX,y:a.pageY},c=this._normValueFromMouse(b);return this._slide(a,this._handleIndex,c),!1},_mouseStop:function(a){return this.handles.removeClass("ui-state-active"),this._mouseSliding=!1,this._stop(a,this._handleIndex),this._change(a,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(a){var b,c,d,e,f;return this.orientation==="horizontal"?(b=this.elementSize.width,c=a.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(b=this.elementSize.height,c=a.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),d=c/b,d>1&&(d=1),d<0&&(d=0),this.orientation==="vertical"&&(d=1-d),e=this._valueMax()-this._valueMin(),f=this._valueMin()+d*e,this._trimAlignValue(f)},_start:function(a,b){var c={handle:this.handles[b],value:this.value()};return this.options.values&&this.options.values.length&&(c.value=this.values(b),c.values=this.values()),this._trigger("start",a,c)},_slide:function(a,b,c){var d,e,f;this.options.values&&this.options.values.length?(d=this.values(b?0:1),this.options.values.length===2&&this.options.range===!0&&(b===0&&c>d||b===1&&c<d)&&(c=d),c!==this.values(b)&&(e=this.values(),e[b]=c,f=this._trigger("slide",a,{handle:this.handles[b],value:c,values:e}),d=this.values(b?0:1),f!==!1&&this.values(b,c,!0))):c!==this.value()&&(f=this._trigger("slide",a,{handle:this.handles[b],value:c}),f!==!1&&this.value(c))},_stop:function(a,b){var c={handle:this.handles[b],value:this.value()};this.options.values&&this.options.values.length&&(c.value=this.values(b),c.values=this.values()),this._trigger("stop",a,c)},_change:function(a,b){if(!this._keySliding&&!this._mouseSliding){var c={handle:this.handles[b],value:this.value()};this.options.values&&this.options.values.length&&(c.value=this.values(b),c.values=this.values()),this._trigger("change",a,c)}},value:function(a){if(arguments.length){this.options.value=this._trimAlignValue(a),this._refreshValue(),this._change(null,0);return}return this._value()},values:function(b,c){var d,e,f;if(arguments.length>1){this.options.values[b]=this._trimAlignValue(c),this._refreshValue(),this._change(null,b);return}if(!arguments.length)return this._values();if(!a.isArray(arguments[0]))return this.options.values&&this.options.values.length?this._values(b):this.value();d=this.options.values,e=arguments[0];for(f=0;f<d.length;f+=1)d[f]=this._trimAlignValue(e[f]),this._change(null,f);this._refreshValue()},_setOption:function(b,c){var d,e=0;a.isArray(this.options.values)&&(e=this.options.values.length),a.Widget.prototype._setOption.apply(this,arguments);switch(b){case"disabled":c?(this.handles.filter(".ui-state-focus").blur(),this.handles.removeClass("ui-state-hover"),this.handles.propAttr("disabled",!0),this.element.addClass("ui-disabled")):(this.handles.propAttr("disabled",!1),this.element.removeClass("ui-disabled"));break;case"orientation":this._detectOrientation(),this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation),this._refreshValue();break;case"value":this._animateOff=!0,this._refreshValue(),this._change(null,0),this._animateOff=!1;break;case"values":this._animateOff=!0,this._refreshValue();for(d=0;d<e;d+=1)this._change(null,d);this._animateOff=!1}},_value:function(){var a=this.options.value;return a=this._trimAlignValue(a),a},_values:function(a){var b,c,d;if(arguments.length)return b=this.options.values[a],b=this._trimAlignValue(b),b;c=this.options.values.slice();for(d=0;d<c.length;d+=1)c[d]=this._trimAlignValue(c[d]);return c},_trimAlignValue:function(a){if(a<=this._valueMin())return this._valueMin();if(a>=this._valueMax())return this._valueMax();var b=this.options.step>0?this.options.step:1,c=(a-this._valueMin())%b,d=a-c;return Math.abs(c)*2>=b&&(d+=c>0?b:-b),parseFloat(d.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var b=this.options.range,c=this.options,d=this,e=this._animateOff?!1:c.animate,f,g={},h,i,j,k;this.options.values&&this.options.values.length?this.handles.each(function(b,i){f=(d.values(b)-d._valueMin())/(d._valueMax()-d._valueMin())*100,g[d.orientation==="horizontal"?"left":"bottom"]=f+"%",a(this).stop(1,1)[e?"animate":"css"](g,c.animate),d.options.range===!0&&(d.orientation==="horizontal"?(b===0&&d.range.stop(1,1)[e?"animate":"css"]({left:f+"%"},c.animate),b===1&&d.range[e?"animate":"css"]({width:f-h+"%"},{queue:!1,duration:c.animate})):(b===0&&d.range.stop(1,1)[e?"animate":"css"]({bottom:f+"%"},c.animate),b===1&&d.range[e?"animate":"css"]({height:f-h+"%"},{queue:!1,duration:c.animate}))),h=f}):(i=this.value(),j=this._valueMin(),k=this._valueMax(),f=k!==j?(i-j)/(k-j)*100:0,g[d.orientation==="horizontal"?"left":"bottom"]=f+"%",this.handle.stop(1,1)[e?"animate":"css"](g,c.animate),b==="min"&&this.orientation==="horizontal"&&this.range.stop(1,1)[e?"animate":"css"]({width:f+"%"},c.animate),b==="max"&&this.orientation==="horizontal"&&this.range[e?"animate":"css"]({width:100-f+"%"},{queue:!1,duration:c.animate}),b==="min"&&this.orientation==="vertical"&&this.range.stop(1,1)[e?"animate":"css"]({height:f+"%"},c.animate),b==="max"&&this.orientation==="vertical"&&this.range[e?"animate":"css"]({height:100-f+"%"},{queue:!1,duration:c.animate}))}}),a.extend(a.ui.slider,{version:"1.8.21"})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.tabs.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){function e(){return++c}function f(){return++d}var c=0,d=0;a.widget("ui.tabs",{options:{add:null,ajaxOptions:null,cache:!1,cookie:null,collapsible:!1,disable:null,disabled:[],enable:null,event:"click",fx:null,idPrefix:"ui-tabs-",load:null,panelTemplate:"<div></div>",remove:null,select:null,show:null,spinner:"<em>Loading&#8230;</em>",tabTemplate:"<li><a href='#{href}'><span>#{label}</span></a></li>"},_create:function(){this._tabify(!0)},_setOption:function(a,b){if(a=="selected"){if(this.options.collapsible&&b==this.options.selected)return;this.select(b)}else this.options[a]=b,this._tabify()},_tabId:function(a){return a.title&&a.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+e()},_sanitizeSelector:function(a){return a.replace(/:/g,"\\:")},_cookie:function(){var b=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+f());return a.cookie.apply(null,[b].concat(a.makeArray(arguments)))},_ui:function(a,b){return{tab:a,panel:b,index:this.anchors.index(a)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var b=a(this);b.html(b.data("label.tabs")).removeData("label.tabs")})},_tabify:function(c){function m(b,c){b.css("display",""),!a.support.opacity&&c.opacity&&b[0].style.removeAttribute("filter")}var d=this,e=this.options,f=/^#.+/;this.list=this.element.find("ol,ul").eq(0),this.lis=a(" > li:has(a[href])",this.list),this.anchors=this.lis.map(function(){return a("a",this)[0]}),this.panels=a([]),this.anchors.each(function(b,c){var g=a(c).attr("href"),h=g.split("#")[0],i;h&&(h===location.toString().split("#")[0]||(i=a("base")[0])&&h===i.href)&&(g=c.hash,c.href=g);if(f.test(g))d.panels=d.panels.add(d.element.find(d._sanitizeSelector(g)));else if(g&&g!=="#"){a.data(c,"href.tabs",g),a.data(c,"load.tabs",g.replace(/#.*$/,""));var j=d._tabId(c);c.href="#"+j;var k=d.element.find("#"+j);k.length||(k=a(e.panelTemplate).attr("id",j).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(d.panels[b-1]||d.list),k.data("destroy.tabs",!0)),d.panels=d.panels.add(k)}else e.disabled.push(b)}),c?(this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all"),this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all"),this.lis.addClass("ui-state-default ui-corner-top"),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom"),e.selected===b?(location.hash&&this.anchors.each(function(a,b){if(b.hash==location.hash)return e.selected=a,!1}),typeof e.selected!="number"&&e.cookie&&(e.selected=parseInt(d._cookie(),10)),typeof e.selected!="number"&&this.lis.filter(".ui-tabs-selected").length&&(e.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"))),e.selected=e.selected||(this.lis.length?0:-1)):e.selected===null&&(e.selected=-1),e.selected=e.selected>=0&&this.anchors[e.selected]||e.selected<0?e.selected:0,e.disabled=a.unique(e.disabled.concat(a.map(this.lis.filter(".ui-state-disabled"),function(a,b){return d.lis.index(a)}))).sort(),a.inArray(e.selected,e.disabled)!=-1&&e.disabled.splice(a.inArray(e.selected,e.disabled),1),this.panels.addClass("ui-tabs-hide"),this.lis.removeClass("ui-tabs-selected ui-state-active"),e.selected>=0&&this.anchors.length&&(d.element.find(d._sanitizeSelector(d.anchors[e.selected].hash)).removeClass("ui-tabs-hide"),this.lis.eq(e.selected).addClass("ui-tabs-selected ui-state-active"),d.element.queue("tabs",function(){d._trigger("show",null,d._ui(d.anchors[e.selected],d.element.find(d._sanitizeSelector(d.anchors[e.selected].hash))[0]))}),this.load(e.selected)),a(window).bind("unload",function(){d.lis.add(d.anchors).unbind(".tabs"),d.lis=d.anchors=d.panels=null})):e.selected=this.lis.index(this.lis.filter(".ui-tabs-selected")),this.element[e.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible"),e.cookie&&this._cookie(e.selected,e.cookie);for(var g=0,h;h=this.lis[g];g++)a(h)[a.inArray(g,e.disabled)!=-1&&!a(h).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");e.cache===!1&&this.anchors.removeData("cache.tabs"),this.lis.add(this.anchors).unbind(".tabs");if(e.event!=="mouseover"){var i=function(a,b){b.is(":not(.ui-state-disabled)")&&b.addClass("ui-state-"+a)},j=function(a,b){b.removeClass("ui-state-"+a)};this.lis.bind("mouseover.tabs",function(){i("hover",a(this))}),this.lis.bind("mouseout.tabs",function(){j("hover",a(this))}),this.anchors.bind("focus.tabs",function(){i("focus",a(this).closest("li"))}),this.anchors.bind("blur.tabs",function(){j("focus",a(this).closest("li"))})}var k,l;e.fx&&(a.isArray(e.fx)?(k=e.fx[0],l=e.fx[1]):k=l=e.fx);var n=l?function(b,c){a(b).closest("li").addClass("ui-tabs-selected ui-state-active"),c.hide().removeClass("ui-tabs-hide").animate(l,l.duration||"normal",function(){m(c,l),d._trigger("show",null,d._ui(b,c[0]))})}:function(b,c){a(b).closest("li").addClass("ui-tabs-selected ui-state-active"),c.removeClass("ui-tabs-hide"),d._trigger("show",null,d._ui(b,c[0]))},o=k?function(a,b){b.animate(k,k.duration||"normal",function(){d.lis.removeClass("ui-tabs-selected ui-state-active"),b.addClass("ui-tabs-hide"),m(b,k),d.element.dequeue("tabs")})}:function(a,b,c){d.lis.removeClass("ui-tabs-selected ui-state-active"),b.addClass("ui-tabs-hide"),d.element.dequeue("tabs")};this.anchors.bind(e.event+".tabs",function(){var b=this,c=a(b).closest("li"),f=d.panels.filter(":not(.ui-tabs-hide)"),g=d.element.find(d._sanitizeSelector(b.hash));if(c.hasClass("ui-tabs-selected")&&!e.collapsible||c.hasClass("ui-state-disabled")||c.hasClass("ui-state-processing")||d.panels.filter(":animated").length||d._trigger("select",null,d._ui(this,g[0]))===!1)return this.blur(),!1;e.selected=d.anchors.index(this),d.abort();if(e.collapsible){if(c.hasClass("ui-tabs-selected"))return e.selected=-1,e.cookie&&d._cookie(e.selected,e.cookie),d.element.queue("tabs",function(){o(b,f)}).dequeue("tabs"),this.blur(),!1;if(!f.length)return e.cookie&&d._cookie(e.selected,e.cookie),d.element.queue("tabs",function(){n(b,g)}),d.load(d.anchors.index(this)),this.blur(),!1}e.cookie&&d._cookie(e.selected,e.cookie);if(g.length)f.length&&d.element.queue("tabs",function(){o(b,f)}),d.element.queue("tabs",function(){n(b,g)}),d.load(d.anchors.index(this));else throw"jQuery UI Tabs: Mismatching fragment identifier.";a.browser.msie&&this.blur()}),this.anchors.bind("click.tabs",function(){return!1})},_getIndex:function(a){return typeof a=="string"&&(a=this.anchors.index(this.anchors.filter("[href$='"+a+"']"))),a},destroy:function(){var b=this.options;return this.abort(),this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs"),this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all"),this.anchors.each(function(){var b=a.data(this,"href.tabs");b&&(this.href=b);var c=a(this).unbind(".tabs");a.each(["href","load","cache"],function(a,b){c.removeData(b+".tabs")})}),this.lis.unbind(".tabs").add(this.panels).each(function(){a.data(this,"destroy.tabs")?a(this).remove():a(this).removeClass(["ui-state-default","ui-corner-top","ui-tabs-selected","ui-state-active","ui-state-hover","ui-state-focus","ui-state-disabled","ui-tabs-panel","ui-widget-content","ui-corner-bottom","ui-tabs-hide"].join(" "))}),b.cookie&&this._cookie(null,b.cookie),this},add:function(c,d,e){e===b&&(e=this.anchors.length);var f=this,g=this.options,h=a(g.tabTemplate.replace(/#\{href\}/g,c).replace(/#\{label\}/g,d)),i=c.indexOf("#")?this._tabId(a("a",h)[0]):c.replace("#","");h.addClass("ui-state-default ui-corner-top").data("destroy.tabs",!0);var j=f.element.find("#"+i);return j.length||(j=a(g.panelTemplate).attr("id",i).data("destroy.tabs",!0)),j.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide"),e>=this.lis.length?(h.appendTo(this.list),j.appendTo(this.list[0].parentNode)):(h.insertBefore(this.lis[e]),j.insertBefore(this.panels[e])),g.disabled=a.map(g.disabled,function(a,b){return a>=e?++a:a}),this._tabify(),this.anchors.length==1&&(g.selected=0,h.addClass("ui-tabs-selected ui-state-active"),j.removeClass("ui-tabs-hide"),this.element.queue("tabs",function(){f._trigger("show",null,f._ui(f.anchors[0],f.panels[0]))}),this.load(0)),this._trigger("add",null,this._ui(this.anchors[e],this.panels[e])),this},remove:function(b){b=this._getIndex(b);var c=this.options,d=this.lis.eq(b).remove(),e=this.panels.eq(b).remove();return d.hasClass("ui-tabs-selected")&&this.anchors.length>1&&this.select(b+(b+1<this.anchors.length?1:-1)),c.disabled=a.map(a.grep(c.disabled,function(a,c){return a!=b}),function(a,c){return a>=b?--a:a}),this._tabify(),this._trigger("remove",null,this._ui(d.find("a")[0],e[0])),this},enable:function(b){b=this._getIndex(b);var c=this.options;if(a.inArray(b,c.disabled)==-1)return;return this.lis.eq(b).removeClass("ui-state-disabled"),c.disabled=a.grep(c.disabled,function(a,c){return a!=b}),this._trigger("enable",null,this._ui(this.anchors[b],this.panels[b])),this},disable:function(a){a=this._getIndex(a);var b=this,c=this.options;return a!=c.selected&&(this.lis.eq(a).addClass("ui-state-disabled"),c.disabled.push(a),c.disabled.sort(),this._trigger("disable",null,this._ui(this.anchors[a],this.panels[a]))),this},select:function(a){a=this._getIndex(a);if(a==-1)if(this.options.collapsible&&this.options.selected!=-1)a=this.options.selected;else return this;return this.anchors.eq(a).trigger(this.options.event+".tabs"),this},load:function(b){b=this._getIndex(b);var c=this,d=this.options,e=this.anchors.eq(b)[0],f=a.data(e,"load.tabs");this.abort();if(!f||this.element.queue("tabs").length!==0&&a.data(e,"cache.tabs")){this.element.dequeue("tabs");return}this.lis.eq(b).addClass("ui-state-processing");if(d.spinner){var g=a("span",e);g.data("label.tabs",g.html()).html(d.spinner)}return this.xhr=a.ajax(a.extend({},d.ajaxOptions,{url:f,success:function(f,g){c.element.find(c._sanitizeSelector(e.hash)).html(f),c._cleanup(),d.cache&&a.data(e,"cache.tabs",!0),c._trigger("load",null,c._ui(c.anchors[b],c.panels[b]));try{d.ajaxOptions.success(f,g)}catch(h){}},error:function(a,f,g){c._cleanup(),c._trigger("load",null,c._ui(c.anchors[b],c.panels[b]));try{d.ajaxOptions.error(a,f,b,e)}catch(g){}}})),c.element.dequeue("tabs"),this},abort:function(){return this.element.queue([]),this.panels.stop(!1,!0),this.element.queue("tabs",this.element.queue("tabs").splice(-2,2)),this.xhr&&(this.xhr.abort(),delete this.xhr),this._cleanup(),this},url:function(a,b){return this.anchors.eq(a).removeData("cache.tabs").data("load.tabs",b),this},length:function(){return this.anchors.length}}),a.extend(a.ui.tabs,{version:"1.8.21"}),a.extend(a.ui.tabs.prototype,{rotation:null,rotate:function(a,b){var c=this,d=this.options,e=c._rotate||(c._rotate=function(b){clearTimeout(c.rotation),c.rotation=setTimeout(function(){var a=d.selected;c.select(++a<c.anchors.length?a:0)},a),b&&b.stopPropagation()}),f=c._unrotate||(c._unrotate=b?function(a){e()}:function(a){a.clientX&&c.rotate(null)});return a?(this.element.bind("tabsshow",e),this.anchors.bind(d.event+".tabs",f),e()):(clearTimeout(c.rotation),this.element.unbind("tabsshow",e),this.anchors.unbind(d.event+".tabs",f),delete this._rotate,delete this._unrotate),this}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.datepicker.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function($,undefined){function Datepicker(){this.debug=!1,this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},$.extend(this._defaults,this.regional[""]),this.dpDiv=bindHover($('<div id="'+this._mainDivId+'" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'))}function bindHover(a){var b="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return a.bind("mouseout",function(a){var c=$(a.target).closest(b);if(!c.length)return;c.removeClass("ui-state-hover ui-datepicker-prev-hover ui-datepicker-next-hover")}).bind("mouseover",function(c){var d=$(c.target).closest(b);if($.datepicker._isDisabledDatepicker(instActive.inline?a.parent()[0]:instActive.input[0])||!d.length)return;d.parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),d.addClass("ui-state-hover"),d.hasClass("ui-datepicker-prev")&&d.addClass("ui-datepicker-prev-hover"),d.hasClass("ui-datepicker-next")&&d.addClass("ui-datepicker-next-hover")})}function extendRemove(a,b){$.extend(a,b);for(var c in b)if(b[c]==null||b[c]==undefined)a[c]=b[c];return a}function isArray(a){return a&&($.browser.safari&&typeof a=="object"&&a.length||a.constructor&&a.constructor.toString().match(/\Array\(\)/))}$.extend($.ui,{datepicker:{version:"1.8.21"}});var PROP_NAME="datepicker",dpuuid=(new Date).getTime(),instActive;$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){return extendRemove(this._defaults,a||{}),this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase(),inline=nodeName=="div"||nodeName=="span";target.id||(this.uuid+=1,target.id="dp"+this.uuid);var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{}),nodeName=="input"?this._connectDatepicker(target,inst):inline&&this._inlineDatepicker(target,inst)},_newInst:function(a,b){var c=a[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1");return{id:c,input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:b?bindHover($('<div class="'+this._inlineClass+' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>')):this.dpDiv}},_connectDatepicker:function(a,b){var c=$(a);b.append=$([]),b.trigger=$([]);if(c.hasClass(this.markerClassName))return;this._attachments(c,b),c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),this._autoSize(b),$.data(a,PROP_NAME,b),b.settings.disabled&&this._disableDatepicker(a)},_attachments:function(a,b){var c=this._get(b,"appendText"),d=this._get(b,"isRTL");b.append&&b.append.remove(),c&&(b.append=$('<span class="'+this._appendClass+'">'+c+"</span>"),a[d?"before":"after"](b.append)),a.unbind("focus",this._showDatepicker),b.trigger&&b.trigger.remove();var e=this._get(b,"showOn");(e=="focus"||e=="both")&&a.focus(this._showDatepicker);if(e=="button"||e=="both"){var f=this._get(b,"buttonText"),g=this._get(b,"buttonImage");b.trigger=$(this._get(b,"buttonImageOnly")?$("<img/>").addClass(this._triggerClass).attr({src:g,alt:f,title:f}):$('<button type="button"></button>').addClass(this._triggerClass).html(g==""?f:$("<img/>").attr({src:g,alt:f,title:f}))),a[d?"before":"after"](b.trigger),b.trigger.click(function(){return $.datepicker._datepickerShowing&&$.datepicker._lastInput==a[0]?$.datepicker._hideDatepicker():$.datepicker._datepickerShowing&&$.datepicker._lastInput!=a[0]?($.datepicker._hideDatepicker(),$.datepicker._showDatepicker(a[0])):$.datepicker._showDatepicker(a[0]),!1})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var d=function(a){var b=0,c=0;for(var d=0;d<a.length;d++)a[d].length>b&&(b=a[d].length,c=d);return c};b.setMonth(d(this._get(a,c.match(/MM/)?"monthNames":"monthNamesShort"))),b.setDate(d(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=$(a);if(c.hasClass(this.markerClassName))return;c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),$.data(a,PROP_NAME,b),this._setDate(b,this._getDefaultDate(b),!0),this._updateDatepicker(b),this._updateAlternate(b),b.settings.disabled&&this._disableDatepicker(a),b.dpDiv.css("display","block")},_dialogDatepicker:function(a,b,c,d,e){var f=this._dialogInst;if(!f){this.uuid+=1;var g="dp"+this.uuid;this._dialogInput=$('<input type="text" id="'+g+'" style="position: absolute; top: -100px; width: 0px; z-index: -10;"/>'),this._dialogInput.keydown(this._doKeyDown),$("body").append(this._dialogInput),f=this._dialogInst=this._newInst(this._dialogInput,!1),f.settings={},$.data(this._dialogInput[0],PROP_NAME,f)}extendRemove(f.settings,d||{}),b=b&&b.constructor==Date?this._formatDate(f,b):b,this._dialogInput.val(b),this._pos=e?e.length?e:[e.pageX,e.pageY]:null;if(!this._pos){var h=document.documentElement.clientWidth,i=document.documentElement.clientHeight,j=document.documentElement.scrollLeft||document.body.scrollLeft,k=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[h/2-100+j,i/2-150+k]}return this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),f.settings.onSelect=c,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),$.blockUI&&$.blockUI(this.dpDiv),$.data(this._dialogInput[0],PROP_NAME,f),this},_destroyDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();$.removeData(a,PROP_NAME),d=="input"?(c.append.remove(),c.trigger.remove(),b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):(d=="div"||d=="span")&&b.removeClass(this.markerClassName).empty()},_enableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!1,c.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().removeClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").removeAttr("disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b})},_disableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!0,c.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().addClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").attr("disabled","disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b}),this._disabledInputs[this._disabledInputs.length]=a},_isDisabledDatepicker:function(a){if(!a)return!1;for(var b=0;b<this._disabledInputs.length;b++)if(this._disabledInputs[b]==a)return!0;return!1},_getInst:function(a){try{return $.data(a,PROP_NAME)}catch(b){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(a,b,c){var d=this._getInst(a);if(arguments.length==2&&typeof b=="string")return b=="defaults"?$.extend({},$.datepicker._defaults):d?b=="all"?$.extend({},d.settings):this._get(d,b):null;var e=b||{};typeof b=="string"&&(e={},e[b]=c);if(d){this._curInst==d&&this._hideDatepicker();var f=this._getDateDatepicker(a,!0),g=this._getMinMaxDate(d,"min"),h=this._getMinMaxDate(d,"max");extendRemove(d.settings,e),g!==null&&e.dateFormat!==undefined&&e.minDate===undefined&&(d.settings.minDate=this._formatDate(d,g)),h!==null&&e.dateFormat!==undefined&&e.maxDate===undefined&&(d.settings.maxDate=this._formatDate(d,h)),this._attachments($(a),d),this._autoSize(d),this._setDate(d,f),this._updateAlternate(d),this._updateDatepicker(d)}},_changeDatepicker:function(a,b,c){this._optionDatepicker(a,b,c)},_refreshDatepicker:function(a){var b=this._getInst(a);b&&this._updateDatepicker(b)},_setDateDatepicker:function(a,b){var c=this._getInst(a);c&&(this._setDate(c,b),this._updateDatepicker(c),this._updateAlternate(c))},_getDateDatepicker:function(a,b){var c=this._getInst(a);return c&&!c.inline&&this._setDateFromField(c,b),c?this._getDate(c):null},_doKeyDown:function(a){var b=$.datepicker._getInst(a.target),c=!0,d=b.dpDiv.is(".ui-datepicker-rtl");b._keyEvent=!0;if($.datepicker._datepickerShowing)switch(a.keyCode){case 9:$.datepicker._hideDatepicker(),c=!1;break;case 13:var e=$("td."+$.datepicker._dayOverClass+":not(."+$.datepicker._currentClass+")",b.dpDiv);e[0]&&$.datepicker._selectDay(a.target,b.selectedMonth,b.selectedYear,e[0]);var f=$.datepicker._get(b,"onSelect");if(f){var g=$.datepicker._formatDate(b);f.apply(b.input?b.input[0]:null,[g,b])}else $.datepicker._hideDatepicker();return!1;case 27:$.datepicker._hideDatepicker();break;case 33:$.datepicker._adjustDate(a.target,a.ctrlKey?-$.datepicker._get(b,"stepBigMonths"):-$.datepicker._get(b,"stepMonths"),"M");break;case 34:$.datepicker._adjustDate(a.target,a.ctrlKey?+$.datepicker._get(b,"stepBigMonths"):+$.datepicker._get(b,"stepMonths"),"M");break;case 35:(a.ctrlKey||a.metaKey)&&$.datepicker._clearDate(a.target),c=a.ctrlKey||a.metaKey;break;case 36:(a.ctrlKey||a.metaKey)&&$.datepicker._gotoToday(a.target),c=a.ctrlKey||a.metaKey;break;case 37:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,d?1:-1,"D"),c=a.ctrlKey||a.metaKey,a.originalEvent.altKey&&$.datepicker._adjustDate(a.target,a.ctrlKey?-$.datepicker._get(b,"stepBigMonths"):-$.datepicker._get(b,"stepMonths"),"M");break;case 38:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,-7,"D"),c=a.ctrlKey||a.metaKey;break;case 39:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,d?-1:1,"D"),c=a.ctrlKey||a.metaKey,a.originalEvent.altKey&&$.datepicker._adjustDate(a.target,a.ctrlKey?+$.datepicker._get(b,"stepBigMonths"):+$.datepicker._get(b,"stepMonths"),"M");break;case 40:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,7,"D"),c=a.ctrlKey||a.metaKey;break;default:c=!1}else a.keyCode==36&&a.ctrlKey?$.datepicker._showDatepicker(this):c=!1;c&&(a.preventDefault(),a.stopPropagation())},_doKeyPress:function(a){var b=$.datepicker._getInst(a.target);if($.datepicker._get(b,"constrainInput")){var c=$.datepicker._possibleChars($.datepicker._get(b,"dateFormat")),d=String.fromCharCode(a.charCode==undefined?a.keyCode:a.charCode);return a.ctrlKey||a.metaKey||d<" "||!c||c.indexOf(d)>-1}},_doKeyUp:function(a){var b=$.datepicker._getInst(a.target);if(b.input.val()!=b.lastVal)try{var c=$.datepicker.parseDate($.datepicker._get(b,"dateFormat"),b.input?b.input.val():null,$.datepicker._getFormatConfig(b));c&&($.datepicker._setDateFromField(b),$.datepicker._updateAlternate(b),$.datepicker._updateDatepicker(b))}catch(d){$.datepicker.log(d)}return!0},_showDatepicker:function(a){a=a.target||a,a.nodeName.toLowerCase()!="input"&&(a=$("input",a.parentNode)[0]);if($.datepicker._isDisabledDatepicker(a)||$.datepicker._lastInput==a)return;var b=$.datepicker._getInst(a);$.datepicker._curInst&&$.datepicker._curInst!=b&&($.datepicker._curInst.dpDiv.stop(!0,!0),b&&$.datepicker._datepickerShowing&&$.datepicker._hideDatepicker($.datepicker._curInst.input[0]));var c=$.datepicker._get(b,"beforeShow"),d=c?c.apply(a,[a,b]):{};if(d===!1)return;extendRemove(b.settings,d),b.lastVal=null,$.datepicker._lastInput=a,$.datepicker._setDateFromField(b),$.datepicker._inDialog&&(a.value=""),$.datepicker._pos||($.datepicker._pos=$.datepicker._findPos(a),$.datepicker._pos[1]+=a.offsetHeight);var e=!1;$(a).parents().each(function(){return e|=$(this).css("position")=="fixed",!e}),e&&$.browser.opera&&($.datepicker._pos[0]-=document.documentElement.scrollLeft,$.datepicker._pos[1]-=document.documentElement.scrollTop);var f={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null,b.dpDiv.empty(),b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),$.datepicker._updateDatepicker(b),f=$.datepicker._checkOffset(b,f,e),b.dpDiv.css({position:$.datepicker._inDialog&&$.blockUI?"static":e?"fixed":"absolute",display:"none",left:f.left+"px",top:f.top+"px"});if(!b.inline){var g=$.datepicker._get(b,"showAnim"),h=$.datepicker._get(b,"duration"),i=function(){var a=b.dpDiv.find("iframe.ui-datepicker-cover");if(!!a.length){var c=$.datepicker._getBorders(b.dpDiv);a.css({left:-c[0],top:-c[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})}};b.dpDiv.zIndex($(a).zIndex()+1),$.datepicker._datepickerShowing=!0,$.effects&&$.effects[g]?b.dpDiv.show(g,$.datepicker._get(b,"showOptions"),h,i):b.dpDiv[g||"show"](g?h:null,i),(!g||!h)&&i(),b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus(),$.datepicker._curInst=b}},_updateDatepicker:function(a){var b=this;b.maxRows=4;var c=$.datepicker._getBorders(a.dpDiv);instActive=a,a.dpDiv.empty().append(this._generateHTML(a));var d=a.dpDiv.find("iframe.ui-datepicker-cover");!d.length||d.css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()}),a.dpDiv.find("."+this._dayOverClass+" a").mouseover();var e=this._getNumberOfMonths(a),f=e[1],g=17;a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),f>1&&a.dpDiv.addClass("ui-datepicker-multi-"+f).css("width",g*f+"em"),a.dpDiv[(e[0]!=1||e[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi"),a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),a==$.datepicker._curInst&&$.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input[0]!=document.activeElement&&a.input.focus();if(a.yearshtml){var h=a.yearshtml;setTimeout(function(){h===a.yearshtml&&a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml),h=a.yearshtml=null},0)}},_getBorders:function(a){var b=function(a){return{thin:1,medium:2,thick:3}[a]||a};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var d=a.dpDiv.outerWidth(),e=a.dpDiv.outerHeight(),f=a.input?a.input.outerWidth():0,g=a.input?a.input.outerHeight():0,h=document.documentElement.clientWidth+$(document).scrollLeft(),i=document.documentElement.clientHeight+$(document).scrollTop();return b.left-=this._get(a,"isRTL")?d-f:0,b.left-=c&&b.left==a.input.offset().left?$(document).scrollLeft():0,b.top-=c&&b.top==a.input.offset().top+g?$(document).scrollTop():0,b.left-=Math.min(b.left,b.left+d>h&&h>d?Math.abs(b.left+d-h):0),b.top-=Math.min(b.top,b.top+e>i&&i>e?Math.abs(e+g):0),b},_findPos:function(a){var b=this._getInst(a),c=this._get(b,"isRTL");while(a&&(a.type=="hidden"||a.nodeType!=1||$.expr.filters.hidden(a)))a=a[c?"previousSibling":"nextSibling"];var d=$(a).offset();return[d.left,d.top]},_hideDatepicker:function(a){var b=this._curInst;if(!b||a&&b!=$.data(a,PROP_NAME))return;if(this._datepickerShowing){var c=this._get(b,"showAnim"),d=this._get(b,"duration"),e=function(){$.datepicker._tidyDialog(b)};$.effects&&$.effects[c]?b.dpDiv.hide(c,$.datepicker._get(b,"showOptions"),d,e):b.dpDiv[c=="slideDown"?"slideUp":c=="fadeIn"?"fadeOut":"hide"](c?d:null,e),c||e(),this._datepickerShowing=!1;var f=this._get(b,"onClose");f&&f.apply(b.input?b.input[0]:null,[b.input?b.input.val():"",b]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),$.blockUI&&($.unblockUI(),$("body").append(this.dpDiv))),this._inDialog=!1}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(a){if(!$.datepicker._curInst)return;var b=$(a.target),c=$.datepicker._getInst(b[0]);(b[0].id!=$.datepicker._mainDivId&&b.parents("#"+$.datepicker._mainDivId).length==0&&!b.hasClass($.datepicker.markerClassName)&&!b.closest("."+$.datepicker._triggerClass).length&&$.datepicker._datepickerShowing&&(!$.datepicker._inDialog||!$.blockUI)||b.hasClass($.datepicker.markerClassName)&&$.datepicker._curInst!=c)&&$.datepicker._hideDatepicker()},_adjustDate:function(a,b,c){var d=$(a),e=this._getInst(d[0]);if(this._isDisabledDatepicker(d[0]))return;this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"):0),c),this._updateDatepicker(e)},_gotoToday:function(a){var b=$(a),c=this._getInst(b[0]);if(this._get(c,"gotoCurrent")&&c.currentDay)c.selectedDay=c.currentDay,c.drawMonth=c.selectedMonth=c.currentMonth,c.drawYear=c.selectedYear=c.currentYear;else{var d=new Date;c.selectedDay=d.getDate(),c.drawMonth=c.selectedMonth=d.getMonth(),c.drawYear=c.selectedYear=d.getFullYear()}this._notifyChange(c),this._adjustDate(b)},_selectMonthYear:function(a,b,c){var d=$(a),e=this._getInst(d[0]);e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10),this._notifyChange(e),this._adjustDate(d)},_selectDay:function(a,b,c,d){var e=$(a);if($(d).hasClass(this._unselectableClass)||this._isDisabledDatepicker(e[0]))return;var f=this._getInst(e[0]);f.selectedDay=f.currentDay=$("a",d).html(),f.selectedMonth=f.currentMonth=b,f.selectedYear=f.currentYear=c,this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))},_clearDate:function(a){var b=$(a),c=this._getInst(b[0]);this._selectDate(b,"")},_selectDate:function(a,b){var c=$(a),d=this._getInst(c[0]);b=b!=null?b:this._formatDate(d),d.input&&d.input.val(b),this._updateAlternate(d);var e=this._get(d,"onSelect");e?e.apply(d.input?d.input[0]:null,[b,d]):d.input&&d.input.trigger("change"),d.inline?this._updateDatepicker(d):(this._hideDatepicker(),this._lastInput=d.input[0],typeof d.input[0]!="object"&&d.input.focus(),this._lastInput=null)},_updateAlternate:function(a){var b=this._get(a,"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),d=this._getDate(a),e=this.formatDate(c,d,this._getFormatConfig(a));$(b).each(function(){$(this).val(e)})}},noWeekends:function(a){var b=a.getDay();return[b>0&&b<6,""]},iso8601Week:function(a){var b=new Date(a.getTime());b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"?b.toString():b+"";if(b=="")return null;var d=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff;d=typeof d!="string"?d:(new Date).getFullYear()%100+parseInt(d,10);var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,g=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,h=(c?c.monthNames:null)||this._defaults.monthNames,i=-1,j=-1,k=-1,l=-1,m=!1,n=function(b){var c=s+1<a.length&&a.charAt(s+1)==b;return c&&s++,c},o=function(a){var c=n(a),d=a=="@"?14:a=="!"?20:a=="y"&&c?4:a=="o"?3:2,e=new RegExp("^\\d{1,"+d+"}"),f=b.substring(r).match(e);if(!f)throw"Missing number at position "+r;return r+=f[0].length,parseInt(f[0],10)},p=function(a,c,d){var e=$.map(n(a)?d:c,function(a,b){return[[b,a]]}).sort(function(a,b){return-(a[1].length-b[1].length)}),f=-1;$.each(e,function(a,c){var d=c[1];if(b.substr(r,d.length).toLowerCase()==d.toLowerCase())return f=c[0],r+=d.length,!1});if(f!=-1)return f+1;throw"Unknown name at position "+r},q=function(){if(b.charAt(r)!=a.charAt(s))throw"Unexpected literal at position "+r;r++},r=0;for(var s=0;s<a.length;s++)if(m)a.charAt(s)=="'"&&!n("'")?m=!1:q();else switch(a.charAt(s)){case"d":k=o("d");break;case"D":p("D",e,f);break;case"o":l=o("o");break;case"m":j=o("m");break;case"M":j=p("M",g,h);break;case"y":i=o("y");break;case"@":var t=new Date(o("@"));i=t.getFullYear(),j=t.getMonth()+1,k=t.getDate();break;case"!":var t=new Date((o("!")-this._ticksTo1970)/1e4);i=t.getFullYear(),j=t.getMonth()+1,k=t.getDate();break;case"'":n("'")?q():m=!0;break;default:q()}if(r<b.length)throw"Extra/unparsed characters found in date: "+b.substring(r);i==-1?i=(new Date).getFullYear():i<100&&(i+=(new Date).getFullYear()-(new Date).getFullYear()%100+(i<=d?0:-100));if(l>-1){j=1,k=l;do{var u=this._getDaysInMonth(i,j-1);if(k<=u)break;j++,k-=u}while(!0)}var t=this._daylightSavingAdjust(new Date(i,j-1,k));if(t.getFullYear()!=i||t.getMonth()+1!=j||t.getDate()!=k)throw"Invalid date";return t},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1e7,formatDate:function(a,b,c){if(!b)return"";var d=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,e=(c?c.dayNames:null)||this._defaults.dayNames,f=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,h=function(b){var c=m+1<a.length&&a.charAt(m+1)==b;return c&&m++,c},i=function(a,b,c){var d=""+b;if(h(a))while(d.length<c)d="0"+d;return d},j=function(a,b,c,d){return h(a)?d[b]:c[b]},k="",l=!1;if(b)for(var m=0;m<a.length;m++)if(l)a.charAt(m)=="'"&&!h("'")?l=!1:k+=a.charAt(m);else switch(a.charAt(m)){case"d":k+=i("d",b.getDate(),2);break;case"D":k+=j("D",b.getDay(),d,e);break;case"o":k+=i("o",Math.round(((new Date(b.getFullYear(),b.getMonth(),b.getDate())).getTime()-(new Date(b.getFullYear(),0,0)).getTime())/864e5),3);break;case"m":k+=i("m",b.getMonth()+1,2);break;case"M":k+=j("M",b.getMonth(),f,g);break;case"y":k+=h("y")?b.getFullYear():(b.getYear()%100<10?"0":"")+b.getYear()%100;break;case"@":k+=b.getTime();break;case"!":k+=b.getTime()*1e4+this._ticksTo1970;break;case"'":h("'")?k+="'":l=!0;break;default:k+=a.charAt(m)}return k},_possibleChars:function(a){var b="",c=!1,d=function(b){var c=e+1<a.length&&a.charAt(e+1)==b;return c&&e++,c};for(var e=0;e<a.length;e++)if(c)a.charAt(e)=="'"&&!d("'")?c=!1:b+=a.charAt(e);else switch(a.charAt(e)){case"d":case"m":case"y":case"@":b+="0123456789";break;case"D":case"M":return null;case"'":d("'")?b+="'":c=!0;break;default:b+=a.charAt(e)}return b},_get:function(a,b){return a.settings[b]!==undefined?a.settings[b]:this._defaults[b]},_setDateFromField:function(a,b){if(a.input.val()==a.lastVal)return;var c=this._get(a,"dateFormat"),d=a.lastVal=a.input?a.input.val():null,e,f;e=f=this._getDefaultDate(a);var g=this._getFormatConfig(a);try{e=this.parseDate(c,d,g)||f}catch(h){this.log(h),d=b?"":d}a.selectedDay=e.getDate(),a.drawMonth=a.selectedMonth=e.getMonth(),a.drawYear=a.selectedYear=e.getFullYear(),a.currentDay=d?e.getDate():0,a.currentMonth=d?e.getMonth():0,a.currentYear=d?e.getFullYear():0,this._adjustInstDate(a)},_getDefaultDate:function(a){return this._restrictMinMax(a,this._determineDate(a,this._get(a,"defaultDate"),new Date))},_determineDate:function(a,b,c){var d=function(a){var b=new Date;return b.setDate(b.getDate()+a),b},e=function(b){try{return $.datepicker.parseDate($.datepicker._get(a,"dateFormat"),b,$.datepicker._getFormatConfig(a))}catch(c){}var d=(b.toLowerCase().match(/^c/)?$.datepicker._getDate(a):null)||new Date,e=d.getFullYear(),f=d.getMonth(),g=d.getDate(),h=/([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,i=h.exec(b);while(i){switch(i[2]||"d"){case"d":case"D":g+=parseInt(i[1],10);break;case"w":case"W":g+=parseInt(i[1],10)*7;break;case"m":case"M":f+=parseInt(i[1],10),g=Math.min(g,$.datepicker._getDaysInMonth(e,f));break;case"y":case"Y":e+=parseInt(i[1],10),g=Math.min(g,$.datepicker._getDaysInMonth(e,f))}i=h.exec(b)}return new Date(e,f,g)},f=b==null||b===""?c:typeof b=="string"?e(b):typeof b=="number"?isNaN(b)?c:d(b):new Date(b.getTime());return f=f&&f.toString()=="Invalid Date"?c:f,f&&(f.setHours(0),f.setMinutes(0),f.setSeconds(0),f.setMilliseconds(0)),this._daylightSavingAdjust(f)},_daylightSavingAdjust:function(a){return a?(a.setHours(a.getHours()>12?a.getHours()+2:0),a):null},_setDate:function(a,b,c){var d=!b,e=a.selectedMonth,f=a.selectedYear,g=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=a.currentDay=g.getDate(),a.drawMonth=a.selectedMonth=a.currentMonth=g.getMonth(),a.drawYear=a.selectedYear=a.currentYear=g.getFullYear(),(e!=a.selectedMonth||f!=a.selectedYear)&&!c&&this._notifyChange(a),this._adjustInstDate(a),a.input&&a.input.val(d?"":this._formatDate(a))},_getDate:function(a){var b=!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return b},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),d=this._get(a,"showButtonPanel"),e=this._get(a,"hideIfNoPrevNext"),f=this._get(a,"navigationAsDateFormat"),g=this._getNumberOfMonths(a),h=this._get(a,"showCurrentAtPos"),i=this._get(a,"stepMonths"),j=g[0]!=1||g[1]!=1,k=this._daylightSavingAdjust(a.currentDay?new Date(a.currentYear,a.currentMonth,a.currentDay):new Date(9999,9,9)),l=this._getMinMaxDate(a,"min"),m=this._getMinMaxDate(a,"max"),n=a.drawMonth-h,o=a.drawYear;n<0&&(n+=12,o--);if(m){var p=this._daylightSavingAdjust(new Date(m.getFullYear(),m.getMonth()-g[0]*g[1]+1,m.getDate()));p=l&&p<l?l:p;while(this._daylightSavingAdjust(new Date(o,n,1))>p)n--,n<0&&(n=11,o--)}a.drawMonth=n,a.drawYear=o;var q=this._get(a,"prevText");q=f?this.formatDate(q,this._daylightSavingAdjust(new Date(o,n-i,1)),this._getFormatConfig(a)):q;var r=this._canAdjustMonth(a,-1,o,n)?'<a class="ui-datepicker-prev ui-corner-all" onclick="DP_jQuery_'+dpuuid+".datepicker._adjustDate('#"+a.id+"', -"+i+", 'M');\""+' title="'+q+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"e":"w")+'">'+q+"</span></a>":e?"":'<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+q+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"e":"w")+'">'+q+"</span></a>",s=this._get(a,"nextText");s=f?this.formatDate(s,this._daylightSavingAdjust(new Date(o,n+i,1)),this._getFormatConfig(a)):s;var t=this._canAdjustMonth(a,1,o,n)?'<a class="ui-datepicker-next ui-corner-all" onclick="DP_jQuery_'+dpuuid+".datepicker._adjustDate('#"+a.id+"', +"+i+", 'M');\""+' title="'+s+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"w":"e")+'">'+s+"</span></a>":e?"":'<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+s+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"w":"e")+'">'+s+"</span></a>",u=this._get(a,"currentText"),v=this._get(a,"gotoCurrent")&&a.currentDay?k:b;u=f?this.formatDate(u,v,this._getFormatConfig(a)):u;var w=a.inline?"":'<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" onclick="DP_jQuery_'+dpuuid+'.datepicker._hideDatepicker();">'+this._get(a,"closeText")+"</button>",x=d?'<div class="ui-datepicker-buttonpane ui-widget-content">'+(c?w:"")+(this._isInRange(a,v)?'<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" onclick="DP_jQuery_'+dpuuid+".datepicker._gotoToday('#"+a.id+"');\""+">"+u+"</button>":"")+(c?"":w)+"</div>":"",y=parseInt(this._get(a,"firstDay"),10);y=isNaN(y)?0:y;var z=this._get(a,"showWeek"),A=this._get(a,"dayNames"),B=this._get(a,"dayNamesShort"),C=this._get(a,"dayNamesMin"),D=this._get(a,"monthNames"),E=this._get(a,"monthNamesShort"),F=this._get(a,"beforeShowDay"),G=this._get(a,"showOtherMonths"),H=this._get(a,"selectOtherMonths"),I=this._get(a,"calculateWeek")||this.iso8601Week,J=this._getDefaultDate(a),K="";for(var L=0;L<g[0];L++){var M="";this.maxRows=4;for(var N=0;N<g[1];N++){var O=this._daylightSavingAdjust(new Date(o,n,a.selectedDay)),P=" ui-corner-all",Q="";if(j){Q+='<div class="ui-datepicker-group';if(g[1]>1)switch(N){case 0:Q+=" ui-datepicker-group-first",P=" ui-corner-"+(c?"right":"left");break;case g[1]-1:Q+=" ui-datepicker-group-last",P=" ui-corner-"+(c?"left":"right");break;default:Q+=" ui-datepicker-group-middle",P=""}Q+='">'}Q+='<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix'+P+'">'+(/all|left/.test(P)&&L==0?c?t:r:"")+(/all|right/.test(P)&&L==0?c?r:t:"")+this._generateMonthYearHeader(a,n,o,l,m,L>0||N>0,D,E)+'</div><table class="ui-datepicker-calendar"><thead>'+"<tr>";var R=z?'<th class="ui-datepicker-week-col">'+this._get(a,"weekHeader")+"</th>":"";for(var S=0;S<7;S++){var T=(S+y)%7;R+="<th"+((S+y+6)%7>=5?' class="ui-datepicker-week-end"':"")+">"+'<span title="'+A[T]+'">'+C[T]+"</span></th>"}Q+=R+"</tr></thead><tbody>";var U=this._getDaysInMonth(o,n);o==a.selectedYear&&n==a.selectedMonth&&(a.selectedDay=Math.min(a.selectedDay,U));var V=(this._getFirstDayOfMonth(o,n)-y+7)%7,W=Math.ceil((V+U)/7),X=j?this.maxRows>W?this.maxRows:W:W;this.maxRows=X;var Y=this._daylightSavingAdjust(new Date(o,n,1-V));for(var Z=0;Z<X;Z++){Q+="<tr>";var _=z?'<td class="ui-datepicker-week-col">'+this._get(a,"calculateWeek")(Y)+"</td>":"";for(var S=0;S<7;S++){var ba=F?F.apply(a.input?a.input[0]:null,[Y]):[!0,""],bb=Y.getMonth()!=n,bc=bb&&!H||!ba[0]||l&&Y<l||m&&Y>m;_+='<td class="'+((S+y+6)%7>=5?" ui-datepicker-week-end":"")+(bb?" ui-datepicker-other-month":"")+(Y.getTime()==O.getTime()&&n==a.selectedMonth&&a._keyEvent||J.getTime()==Y.getTime()&&J.getTime()==O.getTime()?" "+this._dayOverClass:"")+(bc?" "+this._unselectableClass+" ui-state-disabled":"")+(bb&&!G?"":" "+ba[1]+(Y.getTime()==k.getTime()?" "+this._currentClass:"")+(Y.getTime()==b.getTime()?" ui-datepicker-today":""))+'"'+((!bb||G)&&ba[2]?' title="'+ba[2]+'"':"")+(bc?"":' onclick="DP_jQuery_'+dpuuid+".datepicker._selectDay('#"+a.id+"',"+Y.getMonth()+","+Y.getFullYear()+', this);return false;"')+">"+(bb&&!G?"&#xa0;":bc?'<span class="ui-state-default">'+Y.getDate()+"</span>":'<a class="ui-state-default'+(Y.getTime()==b.getTime()?" ui-state-highlight":"")+(Y.getTime()==k.getTime()?" ui-state-active":"")+(bb?" ui-priority-secondary":"")+'" href="#">'+Y.getDate()+"</a>")+"</td>",Y.setDate(Y.getDate()+1),Y=this._daylightSavingAdjust(Y)}Q+=_+"</tr>"}n++,n>11&&(n=0,o++),Q+="</tbody></table>"+(j?"</div>"+(g[0]>0&&N==g[1]-1?'<div class="ui-datepicker-row-break"></div>':""):""),M+=Q}K+=M}return K+=x+($.browser.msie&&parseInt($.browser.version,10)<7&&!a.inline?'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>':""),a._keyEvent=!1,K},_generateMonthYearHeader:function(a,b,c,d,e,f,g,h){var i=this._get(a,"changeMonth"),j=this._get(a,"changeYear"),k=this._get(a,"showMonthAfterYear"),l='<div class="ui-datepicker-title">',m="";if(f||!i)m+='<span class="ui-datepicker-month">'+g[b]+"</span>";else{var n=d&&d.getFullYear()==c,o=e&&e.getFullYear()==c;m+='<select class="ui-datepicker-month" onchange="DP_jQuery_'+dpuuid+".datepicker._selectMonthYear('#"+a.id+"', this, 'M');\" "+">";for(var p=0;p<12;p++)(!n||p>=d.getMonth())&&(!o||p<=e.getMonth())&&(m+='<option value="'+p+'"'+(p==b?' selected="selected"':"")+">"+h[p]+"</option>");m+="</select>"}k||(l+=m+(f||!i||!j?"&#xa0;":""));if(!a.yearshtml){a.yearshtml="";if(f||!j)l+='<span class="ui-datepicker-year">'+c+"</span>";else{var q=this._get(a,"yearRange").split(":"),r=(new Date).getFullYear(),s=function(a){var b=a.match(/c[+-].*/)?c+parseInt(a.substring(1),10):a.match(/[+-].*/)?r+parseInt(a,10):parseInt(a,10);return isNaN(b)?r:b},t=s(q[0]),u=Math.max(t,s(q[1]||""));t=d?Math.max(t,d.getFullYear()):t,u=e?Math.min(u,e.getFullYear()):u,a.yearshtml+='<select class="ui-datepicker-year" onchange="DP_jQuery_'+dpuuid+".datepicker._selectMonthYear('#"+a.id+"', this, 'Y');\" "+">";for(;t<=u;t++)a.yearshtml+='<option value="'+t+'"'+(t==c?' selected="selected"':"")+">"+t+"</option>";a.yearshtml+="</select>",l+=a.yearshtml,a.yearshtml=null}}return l+=this._get(a,"yearSuffix"),k&&(l+=(f||!i||!j?"&#xa0;":"")+m),l+="</div>",l},_adjustInstDate:function(a,b,c){var d=a.drawYear+(c=="Y"?b:0),e=a.drawMonth+(c=="M"?b:0),f=Math.min(a.selectedDay,this._getDaysInMonth(d,e))+(c=="D"?b:0),g=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(d,e,f)));a.selectedDay=g.getDate(),a.drawMonth=a.selectedMonth=g.getMonth(),a.drawYear=a.selectedYear=g.getFullYear(),(c=="M"||c=="Y")&&this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max"),e=c&&b<c?c:b;return e=d&&e>d?d:e,e},_notifyChange:function(a){var b=this._get(a,"onChangeMonthYear");b&&b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){var b=this._get(a,"numberOfMonths");return b==null?[1,1]:typeof b=="number"?[1,b]:b},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-this._daylightSavingAdjust(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,d){var e=this._getNumberOfMonths(a),f=this._daylightSavingAdjust(new Date(c,d+(b<0?b:e[0]*e[1]),1));return b<0&&f.setDate(this._getDaysInMonth(f.getFullYear(),f.getMonth())),this._isInRange(a,f)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!d||b.getTime()<=d.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");return b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10),{shortYearCutoff:b,dayNamesShort:this._get(a,"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,d){b||(a.currentDay=a.selectedDay,a.currentMonth=a.selectedMonth,a.currentYear=a.selectedYear);var e=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(d,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),e,this._getFormatConfig(a))}}),$.fn.datepicker=function(a){if(!this.length)return this;$.datepicker.initialized||($(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv),$.datepicker.initialized=!0);var b=Array.prototype.slice.call(arguments,1);return typeof a!="string"||a!="isDisabled"&&a!="getDate"&&a!="widget"?a=="option"&&arguments.length==2&&typeof arguments[1]=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b)):this.each(function(){typeof a=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this].concat(b)):$.datepicker._attachDatepicker(this,a)}):$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b))},$.datepicker=new Datepicker,$.datepicker.initialized=!1,$.datepicker.uuid=(new Date).getTime(),$.datepicker.version="1.8.21",window["DP_jQuery_"+dpuuid]=$})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.progressbar.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.widget("ui.progressbar",{options:{value:0,max:100},min:0,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.options.max,"aria-valuenow":this._value()}),this.valueDiv=a("<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>").appendTo(this.element),this.oldValue=this._value(),this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.valueDiv.remove(),a.Widget.prototype.destroy.apply(this,arguments)},value:function(a){return a===b?this._value():(this._setOption("value",a),this)},_setOption:function(b,c){b==="value"&&(this.options.value=c,this._refreshValue(),this._value()===this.options.max&&this._trigger("complete")),a.Widget.prototype._setOption.apply(this,arguments)},_value:function(){var a=this.options.value;return typeof a!="number"&&(a=0),Math.min(this.options.max,Math.max(this.min,a))},_percentage:function(){return 100*this._value()/this.options.max},_refreshValue:function(){var a=this.value(),b=this._percentage();this.oldValue!==a&&(this.oldValue=a,this._trigger("change")),this.valueDiv.toggle(a>this.min).toggleClass("ui-corner-right",a===this.options.max).width(b.toFixed(0)+"%"),this.element.attr("aria-valuenow",a)}}),a.extend(a.ui.progressbar,{version:"1.8.21"})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.effects.core.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+jQuery.effects||function(a,b){function c(b){var c;return b&&b.constructor==Array&&b.length==3?b:(c=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(b))?[parseInt(c[1],10),parseInt(c[2],10),parseInt(c[3],10)]:(c=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(b))?[parseFloat(c[1])*2.55,parseFloat(c[2])*2.55,parseFloat(c[3])*2.55]:(c=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(b))?[parseInt(c[1],16),parseInt(c[2],16),parseInt(c[3],16)]:(c=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(b))?[parseInt(c[1]+c[1],16),parseInt(c[2]+c[2],16),parseInt(c[3]+c[3],16)]:(c=/rgba\(0, 0, 0, 0\)/.exec(b))?e.transparent:e[a.trim(b).toLowerCase()]}function d(b,d){var e;do{e=a.curCSS(b,d);if(e!=""&&e!="transparent"||a.nodeName(b,"body"))break;d="backgroundColor"}while(b=b.parentNode);return c(e)}function h(){var a=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle,b={},c,d;if(a&&a.length&&a[0]&&a[a[0]]){var e=a.length;while(e--)c=a[e],typeof a[c]=="string"&&(d=c.replace(/\-(\w)/g,function(a,b){return b.toUpperCase()}),b[d]=a[c])}else for(c in a)typeof a[c]=="string"&&(b[c]=a[c]);return b}function i(b){var c,d;for(c in b)d=b[c],(d==null||a.isFunction(d)||c in g||/scrollbar/.test(c)||!/color/i.test(c)&&isNaN(parseFloat(d)))&&delete b[c];return b}function j(a,b){var c={_:0},d;for(d in b)a[d]!=b[d]&&(c[d]=b[d]);return c}function k(b,c,d,e){typeof b=="object"&&(e=c,d=null,c=b,b=c.effect),a.isFunction(c)&&(e=c,d=null,c={});if(typeof c=="number"||a.fx.speeds[c])e=d,d=c,c={};return a.isFunction(d)&&(e=d,d=null),c=c||{},d=d||c.duration,d=a.fx.off?0:typeof d=="number"?d:d in a.fx.speeds?a.fx.speeds[d]:a.fx.speeds._default,e=e||c.complete,[b,c,d,e]}function l(b){return!b||typeof b=="number"||a.fx.speeds[b]?!0:typeof b=="string"&&!a.effects[b]?!0:!1}a.effects={},a.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","borderColor","color","outlineColor"],function(b,e){a.fx.step[e]=function(a){a.colorInit||(a.start=d(a.elem,e),a.end=c(a.end),a.colorInit=!0),a.elem.style[e]="rgb("+Math.max(Math.min(parseInt(a.pos*(a.end[0]-a.start[0])+a.start[0],10),255),0)+","+Math.max(Math.min(parseInt(a.pos*(a.end[1]-a.start[1])+a.start[1],10),255),0)+","+Math.max(Math.min(parseInt(a.pos*(a.end[2]-a.start[2])+a.start[2],10),255),0)+")"}});var e={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},f=["add","remove","toggle"],g={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};a.effects.animateClass=function(b,c,d,e){return a.isFunction(d)&&(e=d,d=null),this.queue(function(){var g=a(this),k=g.attr("style")||" ",l=i(h.call(this)),m,n=g.attr("class")||"";a.each(f,function(a,c){b[c]&&g[c+"Class"](b[c])}),m=i(h.call(this)),g.attr("class",n),g.animate(j(l,m),{queue:!1,duration:c,easing:d,complete:function(){a.each(f,function(a,c){b[c]&&g[c+"Class"](b[c])}),typeof g.attr("style")=="object"?(g.attr("style").cssText="",g.attr("style").cssText=k):g.attr("style",k),e&&e.apply(this,arguments),a.dequeue(this)}})})},a.fn.extend({_addClass:a.fn.addClass,addClass:function(b,c,d,e){return c?a.effects.animateClass.apply(this,[{add:b},c,d,e]):this._addClass(b)},_removeClass:a.fn.removeClass,removeClass:function(b,c,d,e){return c?a.effects.animateClass.apply(this,[{remove:b},c,d,e]):this._removeClass(b)},_toggleClass:a.fn.toggleClass,toggleClass:function(c,d,e,f,g){return typeof d=="boolean"||d===b?e?a.effects.animateClass.apply(this,[d?{add:c}:{remove:c},e,f,g]):this._toggleClass(c,d):a.effects.animateClass.apply(this,[{toggle:c},d,e,f])},switchClass:function(b,c,d,e,f){return a.effects.animateClass.apply(this,[{add:c,remove:b},d,e,f])}}),a.extend(a.effects,{version:"1.8.21",save:function(a,b){for(var c=0;c<b.length;c++)b[c]!==null&&a.data("ec.storage."+b[c],a[0].style[b[c]])},restore:function(a,b){for(var c=0;c<b.length;c++)b[c]!==null&&a.css(b[c],a.data("ec.storage."+b[c]))},setMode:function(a,b){return b=="toggle"&&(b=a.is(":hidden")?"show":"hide"),b},getBaseline:function(a,b){var c,d;switch(a[0]){case"top":c=0;break;case"middle":c=.5;break;case"bottom":c=1;break;default:c=a[0]/b.height}switch(a[1]){case"left":d=0;break;case"center":d=.5;break;case"right":d=1;break;default:d=a[1]/b.width}return{x:d,y:c}},createWrapper:function(b){if(b.parent().is(".ui-effects-wrapper"))return b.parent();var c={width:b.outerWidth(!0),height:b.outerHeight(!0),"float":b.css("float")},d=a("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),e=document.activeElement;try{e.id}catch(f){e=document.body}return b.wrap(d),(b[0]===e||a.contains(b[0],e))&&a(e).focus(),d=b.parent(),b.css("position")=="static"?(d.css({position:"relative"}),b.css({position:"relative"})):(a.extend(c,{position:b.css("position"),zIndex:b.css("z-index")}),a.each(["top","left","bottom","right"],function(a,d){c[d]=b.css(d),isNaN(parseInt(c[d],10))&&(c[d]="auto")}),b.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),d.css(c).show()},removeWrapper:function(b){var c,d=document.activeElement;return b.parent().is(".ui-effects-wrapper")?(c=b.parent().replaceWith(b),(b[0]===d||a.contains(b[0],d))&&a(d).focus(),c):b},setTransition:function(b,c,d,e){return e=e||{},a.each(c,function(a,c){var f=b.cssUnit(c);f[0]>0&&(e[c]=f[0]*d+f[1])}),e}}),a.fn.extend({effect:function(b,c,d,e){var f=k.apply(this,arguments),g={options:f[1],duration:f[2],callback:f[3]},h=g.options.mode,i=a.effects[b];return a.fx.off||!i?h?this[h](g.duration,g.callback):this.each(function(){g.callback&&g.callback.call(this)}):i.call(this,g)},_show:a.fn.show,show:function(a){if(l(a))return this._show.apply(this,arguments);var b=k.apply(this,arguments);return b[1].mode="show",this.effect.apply(this,b)},_hide:a.fn.hide,hide:function(a){if(l(a))return this._hide.apply(this,arguments);var b=k.apply(this,arguments);return b[1].mode="hide",this.effect.apply(this,b)},__toggle:a.fn.toggle,toggle:function(b){if(l(b)||typeof b=="boolean"||a.isFunction(b))return this.__toggle.apply(this,arguments);var c=k.apply(this,arguments);return c[1].mode="toggle",this.effect.apply(this,c)},cssUnit:function(b){var c=this.css(b),d=[];return a.each(["em","px","%","pt"],function(a,b){c.indexOf(b)>0&&(d=[parseFloat(c),b])}),d}}),a.easing.jswing=a.easing.swing,a.extend(a.easing,{def:"easeOutQuad",swing:function(b,c,d,e,f){return a.easing[a.easing.def](b,c,d,e,f)},easeInQuad:function(a,b,c,d,e){return d*(b/=e)*b+c},easeOutQuad:function(a,b,c,d,e){return-d*(b/=e)*(b-2)+c},easeInOutQuad:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b+c:-d/2*(--b*(b-2)-1)+c},easeInCubic:function(a,b,c,d,e){return d*(b/=e)*b*b+c},easeOutCubic:function(a,b,c,d,e){return d*((b=b/e-1)*b*b+1)+c},easeInOutCubic:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b*b+c:d/2*((b-=2)*b*b+2)+c},easeInQuart:function(a,b,c,d,e){return d*(b/=e)*b*b*b+c},easeOutQuart:function(a,b,c,d,e){return-d*((b=b/e-1)*b*b*b-1)+c},easeInOutQuart:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b*b*b+c:-d/2*((b-=2)*b*b*b-2)+c},easeInQuint:function(a,b,c,d,e){return d*(b/=e)*b*b*b*b+c},easeOutQuint:function(a,b,c,d,e){return d*((b=b/e-1)*b*b*b*b+1)+c},easeInOutQuint:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b*b*b*b+c:d/2*((b-=2)*b*b*b*b+2)+c},easeInSine:function(a,b,c,d,e){return-d*Math.cos(b/e*(Math.PI/2))+d+c},easeOutSine:function(a,b,c,d,e){return d*Math.sin(b/e*(Math.PI/2))+c},easeInOutSine:function(a,b,c,d,e){return-d/2*(Math.cos(Math.PI*b/e)-1)+c},easeInExpo:function(a,b,c,d,e){return b==0?c:d*Math.pow(2,10*(b/e-1))+c},easeOutExpo:function(a,b,c,d,e){return b==e?c+d:d*(-Math.pow(2,-10*b/e)+1)+c},easeInOutExpo:function(a,b,c,d,e){return b==0?c:b==e?c+d:(b/=e/2)<1?d/2*Math.pow(2,10*(b-1))+c:d/2*(-Math.pow(2,-10*--b)+2)+c},easeInCirc:function(a,b,c,d,e){return-d*(Math.sqrt(1-(b/=e)*b)-1)+c},easeOutCirc:function(a,b,c,d,e){return d*Math.sqrt(1-(b=b/e-1)*b)+c},easeInOutCirc:function(a,b,c,d,e){return(b/=e/2)<1?-d/2*(Math.sqrt(1-b*b)-1)+c:d/2*(Math.sqrt(1-(b-=2)*b)+1)+c},easeInElastic:function(a,b,c,d,e){var f=1.70158,g=0,h=d;if(b==0)return c;if((b/=e)==1)return c+d;g||(g=e*.3);if(h<Math.abs(d)){h=d;var f=g/4}else var f=g/(2*Math.PI)*Math.asin(d/h);return-(h*Math.pow(2,10*(b-=1))*Math.sin((b*e-f)*2*Math.PI/g))+c},easeOutElastic:function(a,b,c,d,e){var f=1.70158,g=0,h=d;if(b==0)return c;if((b/=e)==1)return c+d;g||(g=e*.3);if(h<Math.abs(d)){h=d;var f=g/4}else var f=g/(2*Math.PI)*Math.asin(d/h);return h*Math.pow(2,-10*b)*Math.sin((b*e-f)*2*Math.PI/g)+d+c},easeInOutElastic:function(a,b,c,d,e){var f=1.70158,g=0,h=d;if(b==0)return c;if((b/=e/2)==2)return c+d;g||(g=e*.3*1.5);if(h<Math.abs(d)){h=d;var f=g/4}else var f=g/(2*Math.PI)*Math.asin(d/h);return b<1?-0.5*h*Math.pow(2,10*(b-=1))*Math.sin((b*e-f)*2*Math.PI/g)+c:h*Math.pow(2,-10*(b-=1))*Math.sin((b*e-f)*2*Math.PI/g)*.5+d+c},easeInBack:function(a,c,d,e,f,g){return g==b&&(g=1.70158),e*(c/=f)*c*((g+1)*c-g)+d},easeOutBack:function(a,c,d,e,f,g){return g==b&&(g=1.70158),e*((c=c/f-1)*c*((g+1)*c+g)+1)+d},easeInOutBack:function(a,c,d,e,f,g){return g==b&&(g=1.70158),(c/=f/2)<1?e/2*c*c*(((g*=1.525)+1)*c-g)+d:e/2*((c-=2)*c*(((g*=1.525)+1)*c+g)+2)+d},easeInBounce:function(b,c,d,e,f){return e-a.easing.easeOutBounce(b,f-c,0,e,f)+d},easeOutBounce:function(a,b,c,d,e){return(b/=e)<1/2.75?d*7.5625*b*b+c:b<2/2.75?d*(7.5625*(b-=1.5/2.75)*b+.75)+c:b<2.5/2.75?d*(7.5625*(b-=2.25/2.75)*b+.9375)+c:d*(7.5625*(b-=2.625/2.75)*b+.984375)+c},easeInOutBounce:function(b,c,d,e,f){return c<f/2?a.easing.easeInBounce(b,c*2,0,e,f)*.5+d:a.easing.easeOutBounce(b,c*2-f,0,e,f)*.5+e*.5+d}})}(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.effects.blind.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.effects.blind=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.direction||"vertical";a.effects.save(c,d),c.show();var g=a.effects.createWrapper(c).css({overflow:"hidden"}),h=f=="vertical"?"height":"width",i=f=="vertical"?g.height():g.width();e=="show"&&g.css(h,0);var j={};j[h]=e=="show"?i:0,g.animate(j,b.duration,b.options.easing,function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()})})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.effects.bounce.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.effects.bounce=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"effect"),f=b.options.direction||"up",g=b.options.distance||20,h=b.options.times||5,i=b.duration||250;/show|hide/.test(e)&&d.push("opacity"),a.effects.save(c,d),c.show(),a.effects.createWrapper(c);var j=f=="up"||f=="down"?"top":"left",k=f=="up"||f=="left"?"pos":"neg",g=b.options.distance||(j=="top"?c.outerHeight({margin:!0})/3:c.outerWidth({margin:!0})/3);e=="show"&&c.css("opacity",0).css(j,k=="pos"?-g:g),e=="hide"&&(g=g/(h*2)),e!="hide"&&h--;if(e=="show"){var l={opacity:1};l[j]=(k=="pos"?"+=":"-=")+g,c.animate(l,i/2,b.options.easing),g=g/2,h--}for(var m=0;m<h;m++){var n={},p={};n[j]=(k=="pos"?"-=":"+=")+g,p[j]=(k=="pos"?"+=":"-=")+g,c.animate(n,i/2,b.options.easing).animate(p,i/2,b.options.easing),g=e=="hide"?g*2:g/2}if(e=="hide"){var l={opacity:0};l[j]=(k=="pos"?"-=":"+=")+g,c.animate(l,i/2,b.options.easing,function(){c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(this,arguments)})}else{var n={},p={};n[j]=(k=="pos"?"-=":"+=")+g,p[j]=(k=="pos"?"+=":"-=")+g,c.animate(n,i/2,b.options.easing).animate(p,i/2,b.options.easing,function(){a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(this,arguments)})}c.queue("fx",function(){c.dequeue()}),c.dequeue()})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.effects.clip.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.effects.clip=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right","height","width"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.direction||"vertical";a.effects.save(c,d),c.show();var g=a.effects.createWrapper(c).css({overflow:"hidden"}),h=c[0].tagName=="IMG"?g:c,i={size:f=="vertical"?"height":"width",position:f=="vertical"?"top":"left"},j=f=="vertical"?h.height():h.width();e=="show"&&(h.css(i.size,0),h.css(i.position,j/2));var k={};k[i.size]=e=="show"?j:0,k[i.position]=e=="show"?0:j/2,h.animate(k,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()}})})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.effects.drop.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.effects.drop=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right","opacity"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.direction||"left";a.effects.save(c,d),c.show(),a.effects.createWrapper(c);var g=f=="up"||f=="down"?"top":"left",h=f=="up"||f=="left"?"pos":"neg",i=b.options.distance||(g=="top"?c.outerHeight({margin:!0})/2:c.outerWidth({margin:!0})/2);e=="show"&&c.css("opacity",0).css(g,h=="pos"?-i:i);var j={opacity:e=="show"?1:0};j[g]=(e=="show"?h=="pos"?"+=":"-=":h=="pos"?"-=":"+=")+i,c.animate(j,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.effects.explode.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.effects.explode=function(b){return this.queue(function(){var c=b.options.pieces?Math.round(Math.sqrt(b.options.pieces)):3,d=b.options.pieces?Math.round(Math.sqrt(b.options.pieces)):3;b.options.mode=b.options.mode=="toggle"?a(this).is(":visible")?"hide":"show":b.options.mode;var e=a(this).show().css("visibility","hidden"),f=e.offset();f.top-=parseInt(e.css("marginTop"),10)||0,f.left-=parseInt(e.css("marginLeft"),10)||0;var g=e.outerWidth(!0),h=e.outerHeight(!0);for(var i=0;i<c;i++)for(var j=0;j<d;j++)e.clone().appendTo("body").wrap("<div></div>").css({position:"absolute",visibility:"visible",left:-j*(g/d),top:-i*(h/c)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:g/d,height:h/c,left:f.left+j*(g/d)+(b.options.mode=="show"?(j-Math.floor(d/2))*(g/d):0),top:f.top+i*(h/c)+(b.options.mode=="show"?(i-Math.floor(c/2))*(h/c):0),opacity:b.options.mode=="show"?0:1}).animate({left:f.left+j*(g/d)+(b.options.mode=="show"?0:(j-Math.floor(d/2))*(g/d)),top:f.top+i*(h/c)+(b.options.mode=="show"?0:(i-Math.floor(c/2))*(h/c)),opacity:b.options.mode=="show"?1:0},b.duration||500);setTimeout(function(){b.options.mode=="show"?e.css({visibility:"visible"}):e.css({visibility:"visible"}).hide(),b.callback&&b.callback.apply(e[0]),e.dequeue(),a("div.ui-effects-explode").remove()},b.duration||500)})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.effects.fade.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.effects.fade=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"hide");c.animate({opacity:d},{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.effects.fold.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.effects.fold=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.size||15,g=!!b.options.horizFirst,h=b.duration?b.duration/2:a.fx.speeds._default/2;a.effects.save(c,d),c.show();var i=a.effects.createWrapper(c).css({overflow:"hidden"}),j=e=="show"!=g,k=j?["width","height"]:["height","width"],l=j?[i.width(),i.height()]:[i.height(),i.width()],m=/([0-9]+)%/.exec(f);m&&(f=parseInt(m[1],10)/100*l[e=="hide"?0:1]),e=="show"&&i.css(g?{height:0,width:f}:{height:f,width:0});var n={},p={};n[k[0]]=e=="show"?l[0]:f,p[k[1]]=e=="show"?l[1]:0,i.animate(n,h,b.options.easing).animate(p,h,b.options.easing,function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()})})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.effects.highlight.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.effects.highlight=function(b){return this.queue(function(){var c=a(this),d=["backgroundImage","backgroundColor","opacity"],e=a.effects.setMode(c,b.options.mode||"show"),f={backgroundColor:c.css("backgroundColor")};e=="hide"&&(f.opacity=0),a.effects.save(c,d),c.show().css({backgroundImage:"none",backgroundColor:b.options.color||"#ffff99"}).animate(f,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){e=="hide"&&c.hide(),a.effects.restore(c,d),e=="show"&&!a.support.opacity&&this.style.removeAttribute("filter"),b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.effects.pulsate.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.effects.pulsate=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"show"),e=(b.options.times||5)*2-1,f=b.duration?b.duration/2:a.fx.speeds._default/2,g=c.is(":visible"),h=0;g||(c.css("opacity",0).show(),h=1),(d=="hide"&&g||d=="show"&&!g)&&e--;for(var i=0;i<e;i++)c.animate({opacity:h},f,b.options.easing),h=(h+1)%2;c.animate({opacity:h},f,b.options.easing,function(){h==0&&c.hide(),b.callback&&b.callback.apply(this,arguments)}),c.queue("fx",function(){c.dequeue()}).dequeue()})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.effects.scale.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.effects.puff=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"hide"),e=parseInt(b.options.percent,10)||150,f=e/100,g={height:c.height(),width:c.width()};a.extend(b.options,{fade:!0,mode:d,percent:d=="hide"?e:100,from:d=="hide"?g:{height:g.height*f,width:g.width*f}}),c.effect("scale",b.options,b.duration,b.callback),c.dequeue()})},a.effects.scale=function(b){return this.queue(function(){var c=a(this),d=a.extend(!0,{},b.options),e=a.effects.setMode(c,b.options.mode||"effect"),f=parseInt(b.options.percent,10)||(parseInt(b.options.percent,10)==0?0:e=="hide"?0:100),g=b.options.direction||"both",h=b.options.origin;e!="effect"&&(d.origin=h||["middle","center"],d.restore=!0);var i={height:c.height(),width:c.width()};c.from=b.options.from||(e=="show"?{height:0,width:0}:i);var j={y:g!="horizontal"?f/100:1,x:g!="vertical"?f/100:1};c.to={height:i.height*j.y,width:i.width*j.x},b.options.fade&&(e=="show"&&(c.from.opacity=0,c.to.opacity=1),e=="hide"&&(c.from.opacity=1,c.to.opacity=0)),d.from=c.from,d.to=c.to,d.mode=e,c.effect("size",d,b.duration,b.callback),c.dequeue()})},a.effects.size=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right","width","height","overflow","opacity"],e=["position","top","bottom","left","right","overflow","opacity"],f=["width","height","overflow"],g=["fontSize"],h=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],i=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],j=a.effects.setMode(c,b.options.mode||"effect"),k=b.options.restore||!1,l=b.options.scale||"both",m=b.options.origin,n={height:c.height(),width:c.width()};c.from=b.options.from||n,c.to=b.options.to||n;if(m){var p=a.effects.getBaseline(m,n);c.from.top=(n.height-c.from.height)*p.y,c.from.left=(n.width-c.from.width)*p.x,c.to.top=(n.height-c.to.height)*p.y,c.to.left=(n.width-c.to.width)*p.x}var q={from:{y:c.from.height/n.height,x:c.from.width/n.width},to:{y:c.to.height/n.height,x:c.to.width/n.width}};if(l=="box"||l=="both")q.from.y!=q.to.y&&(d=d.concat(h),c.from=a.effects.setTransition(c,h,q.from.y,c.from),c.to=a.effects.setTransition(c,h,q.to.y,c.to)),q.from.x!=q.to.x&&(d=d.concat(i),c.from=a.effects.setTransition(c,i,q.from.x,c.from),c.to=a.effects.setTransition(c,i,q.to.x,c.to));(l=="content"||l=="both")&&q.from.y!=q.to.y&&(d=d.concat(g),c.from=a.effects.setTransition(c,g,q.from.y,c.from),c.to=a.effects.setTransition(c,g,q.to.y,c.to)),a.effects.save(c,k?d:e),c.show(),a.effects.createWrapper(c),c.css("overflow","hidden").css(c.from);if(l=="content"||l=="both")h=h.concat(["marginTop","marginBottom"]).concat(g),i=i.concat(["marginLeft","marginRight"]),f=d.concat(h).concat(i),c.find("*[width]").each(function(){var c=a(this);k&&a.effects.save(c,f);var d={height:c.height(),width:c.width()};c.from={height:d.height*q.from.y,width:d.width*q.from.x},c.to={height:d.height*q.to.y,width:d.width*q.to.x},q.from.y!=q.to.y&&(c.from=a.effects.setTransition(c,h,q.from.y,c.from),c.to=a.effects.setTransition(c,h,q.to.y,c.to)),q.from.x!=q.to.x&&(c.from=a.effects.setTransition(c,i,q.from.x,c.from),c.to=a.effects.setTransition(c,i,q.to.x,c.to)),c.css(c.from),c.animate(c.to,b.duration,b.options.easing,function(){k&&a.effects.restore(c,f)})});c.animate(c.to,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){c.to.opacity===0&&c.css("opacity",c.from.opacity),j=="hide"&&c.hide(),a.effects.restore(c,k?d:e),a.effects.removeWrapper(c),b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.effects.shake.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.effects.shake=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"effect"),f=b.options.direction||"left",g=b.options.distance||20,h=b.options.times||3,i=b.duration||b.options.duration||140;a.effects.save(c,d),c.show(),a.effects.createWrapper(c);var j=f=="up"||f=="down"?"top":"left",k=f=="up"||f=="left"?"pos":"neg",l={},m={},n={};l[j]=(k=="pos"?"-=":"+=")+g,m[j]=(k=="pos"?"+=":"-=")+g*2,n[j]=(k=="pos"?"-=":"+=")+g*2,c.animate(l,i,b.options.easing);for(var p=1;p<h;p++)c.animate(m,i,b.options.easing).animate(n,i,b.options.easing);c.animate(m,i,b.options.easing).animate(l,i/2,b.options.easing,function(){a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(this,arguments)}),c.queue("fx",function(){c.dequeue()}),c.dequeue()})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.effects.slide.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.effects.slide=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"show"),f=b.options.direction||"left";a.effects.save(c,d),c.show(),a.effects.createWrapper(c).css({overflow:"hidden"});var g=f=="up"||f=="down"?"top":"left",h=f=="up"||f=="left"?"pos":"neg",i=b.options.distance||(g=="top"?c.outerHeight({margin:!0}):c.outerWidth({margin:!0}));e=="show"&&c.css(g,h=="pos"?isNaN(i)?"-"+i:-i:i);var j={};j[g]=(e=="show"?h=="pos"?"+=":"-=":h=="pos"?"-=":"+=")+i,c.animate(j,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.effects.transfer.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.effects.transfer=function(b){return this.queue(function(){var c=a(this),d=a(b.options.to),e=d.offset(),f={top:e.top,left:e.left,height:d.innerHeight(),width:d.innerWidth()},g=c.offset(),h=a('<div class="ui-effects-transfer"></div>').appendTo(document.body).addClass(b.options.className).css({top:g.top,left:g.left,height:c.innerHeight(),width:c.innerWidth(),position:"absolute"}).animate(f,b.duration,b.options.easing,function(){h.remove(),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()})})}})(jQuery);;
\ No newline at end of file
diff --git a/snf-astakos-app/astakos/im/static/im/js/jquery.alerts.js b/snf-astakos-app/astakos/im/static/im/js/jquery.alerts.js
new file mode 100644 (file)
index 0000000..80b1c85
--- /dev/null
@@ -0,0 +1,235 @@
+// jQuery Alert Dialogs Plugin\r
+//\r
+// Version 1.1\r
+//\r
+// Cory S.N. LaViska\r
+// A Beautiful Site (http://abeautifulsite.net/)\r
+// 14 May 2009\r
+//\r
+// Visit http://abeautifulsite.net/notebook/87 for more information\r
+//\r
+// Usage:\r
+//             jAlert( message, [title, callback] )\r
+//             jConfirm( message, [title, callback] )\r
+//             jPrompt( message, [value, title, callback] )\r
+// \r
+// History:\r
+//\r
+//             1.00 - Released (29 December 2008)\r
+//\r
+//             1.01 - Fixed bug where unbinding would destroy all resize events\r
+//\r
+// License:\r
+// \r
+// This plugin is dual-licensed under the GNU General Public License and the MIT License and\r
+// is copyright 2008 A Beautiful Site, LLC. \r
+//\r
+(function($) {\r
+       \r
+       $.alerts = {\r
+               \r
+               // These properties can be read/written by accessing $.alerts.propertyName from your scripts at any time\r
+               \r
+               verticalOffset: -75,                // vertical offset of the dialog from center screen, in pixels\r
+               horizontalOffset: 0,                // horizontal offset of the dialog from center screen, in pixels/\r
+               repositionOnResize: true,           // re-centers the dialog on window resize\r
+               overlayOpacity: .01,                // transparency level of overlay\r
+               overlayColor: '#FFF',               // base color of overlay\r
+               draggable: true,                    // make the dialogs draggable (requires UI Draggables plugin)\r
+               okButton: '&nbsp;OK&nbsp;',         // text for the OK button\r
+               cancelButton: '&nbsp;Cancel&nbsp;', // text for the Cancel button\r
+               dialogClass: null,                  // if specified, this class will be applied to all dialogs\r
+               \r
+               // Public methods\r
+               \r
+               alert: function(message, title, callback) {\r
+                       if( title == null ) title = 'Alert';\r
+                       $.alerts._show(title, message, null, 'alert', function(result) {\r
+                               if( callback ) callback(result);\r
+                       });\r
+               },\r
+               \r
+               confirm: function(message, title, callback) {\r
+                       if( title == null ) title = 'Confirm';\r
+                       $.alerts._show(title, message, null, 'confirm', function(result) {\r
+                               if( callback ) callback(result);\r
+                       });\r
+               },\r
+                       \r
+               prompt: function(message, value, title, callback) {\r
+                       if( title == null ) title = 'Prompt';\r
+                       $.alerts._show(title, message, value, 'prompt', function(result) {\r
+                               if( callback ) callback(result);\r
+                       });\r
+               },\r
+               \r
+               // Private methods\r
+               \r
+               _show: function(title, msg, value, type, callback) {\r
+                       \r
+                       $.alerts._hide();\r
+                       $.alerts._overlay('show');\r
+                       \r
+                       $("BODY").append(\r
+                         '<div id="popup_container">' +\r
+                           '<h1 id="popup_title"></h1>' +\r
+                           '<div id="popup_content">' +\r
+                             '<div id="popup_message"></div>' +\r
+                               '</div>' +\r
+                         '</div>');\r
+                       \r
+                       if( $.alerts.dialogClass ) $("#popup_container").addClass($.alerts.dialogClass);\r
+                       \r
+                       // IE6 Fix\r
+                       var pos = ($.browser.msie && parseInt($.browser.version) <= 6 ) ? 'absolute' : 'fixed'; \r
+                       \r
+                       $("#popup_container").css({\r
+                               position: pos,\r
+                               zIndex: 99999,\r
+                               padding: 0,\r
+                               margin: 0\r
+                       });\r
+                       \r
+                       $("#popup_title").text(title);\r
+                       $("#popup_content").addClass(type);\r
+                       $("#popup_message").text(msg);\r
+                       $("#popup_message").html( $("#popup_message").text().replace(/\n/g, '<br />') );\r
+                       \r
+                       $("#popup_container").css({\r
+                               minWidth: $("#popup_container").outerWidth(),\r
+                               maxWidth: $("#popup_container").outerWidth()\r
+                       });\r
+                       \r
+                       $.alerts._reposition();\r
+                       $.alerts._maintainPosition(true);\r
+                       \r
+                       switch( type ) {\r
+                               case 'alert':\r
+                                       $("#popup_message").after('<div id="popup_panel"><input type="button" value="' + $.alerts.okButton + '" id="popup_ok" /></div>');\r
+                                       $("#popup_ok").click( function() {\r
+                                               $.alerts._hide();\r
+                                               callback(true);\r
+                                       });\r
+                                       $("#popup_ok").focus().keypress( function(e) {\r
+                                               if( e.keyCode == 13 || e.keyCode == 27 ) $("#popup_ok").trigger('click');\r
+                                       });\r
+                               break;\r
+                               case 'confirm':\r
+                                       $("#popup_message").after('<div id="popup_panel"><input type="button" value="' + $.alerts.okButton + '" id="popup_ok" /> <input type="button" value="' + $.alerts.cancelButton + '" id="popup_cancel" /></div>');\r
+                                       $("#popup_ok").click( function() {\r
+                                               $.alerts._hide();\r
+                                               if( callback ) callback(true);\r
+                                       });\r
+                                       $("#popup_cancel").click( function() {\r
+                                               $.alerts._hide();\r
+                                               if( callback ) callback(false);\r
+                                       });\r
+                                       $("#popup_ok").focus();\r
+                                       $("#popup_ok, #popup_cancel").keypress( function(e) {\r
+                                               if( e.keyCode == 13 ) $("#popup_ok").trigger('click');\r
+                                               if( e.keyCode == 27 ) $("#popup_cancel").trigger('click');\r
+                                       });\r
+                               break;\r
+                               case 'prompt':\r
+                                       $("#popup_message").append('<br /><input type="text" size="30" id="popup_prompt" />').after('<div id="popup_panel"><input type="button" value="' + $.alerts.okButton + '" id="popup_ok" /> <input type="button" value="' + $.alerts.cancelButton + '" id="popup_cancel" /></div>');\r
+                                       $("#popup_prompt").width( $("#popup_message").width() );\r
+                                       $("#popup_ok").click( function() {\r
+                                               var val = $("#popup_prompt").val();\r
+                                               $.alerts._hide();\r
+                                               if( callback ) callback( val );\r
+                                       });\r
+                                       $("#popup_cancel").click( function() {\r
+                                               $.alerts._hide();\r
+                                               if( callback ) callback( null );\r
+                                       });\r
+                                       $("#popup_prompt, #popup_ok, #popup_cancel").keypress( function(e) {\r
+                                               if( e.keyCode == 13 ) $("#popup_ok").trigger('click');\r
+                                               if( e.keyCode == 27 ) $("#popup_cancel").trigger('click');\r
+                                       });\r
+                                       if( value ) $("#popup_prompt").val(value);\r
+                                       $("#popup_prompt").focus().select();\r
+                               break;\r
+                       }\r
+                       \r
+                       // Make draggable\r
+                       if( $.alerts.draggable ) {\r
+                               try {\r
+                                       $("#popup_container").draggable({ handle: $("#popup_title") });\r
+                                       $("#popup_title").css({ cursor: 'move' });\r
+                               } catch(e) { /* requires jQuery UI draggables */ }\r
+                       }\r
+               },\r
+               \r
+               _hide: function() {\r
+                       $("#popup_container").remove();\r
+                       $.alerts._overlay('hide');\r
+                       $.alerts._maintainPosition(false);\r
+               },\r
+               \r
+               _overlay: function(status) {\r
+                       switch( status ) {\r
+                               case 'show':\r
+                                       $.alerts._overlay('hide');\r
+                                       $("BODY").append('<div id="popup_overlay"></div>');\r
+                                       $("#popup_overlay").css({\r
+                                               position: 'absolute',\r
+                                               zIndex: 99998,\r
+                                               top: '0px',\r
+                                               left: '0px',\r
+                                               width: '100%',\r
+                                               height: $(document).height(),\r
+                                               background: $.alerts.overlayColor,\r
+                                               opacity: $.alerts.overlayOpacity\r
+                                       });\r
+                               break;\r
+                               case 'hide':\r
+                                       $("#popup_overlay").remove();\r
+                               break;\r
+                       }\r
+               },\r
+               \r
+               _reposition: function() {\r
+                       var top = (($(window).height() / 2) - ($("#popup_container").outerHeight() / 2)) + $.alerts.verticalOffset;\r
+                       var left = (($(window).width() / 2) - ($("#popup_container").outerWidth() / 2)) + $.alerts.horizontalOffset;\r
+                       if( top < 0 ) top = 0;\r
+                       if( left < 0 ) left = 0;\r
+                       \r
+                       // IE6 fix\r
+                       if( $.browser.msie && parseInt($.browser.version) <= 6 ) top = top + $(window).scrollTop();\r
+                       \r
+                       $("#popup_container").css({\r
+                               top: top + 'px',\r
+                               left: left + 'px'\r
+                       });\r
+                       $("#popup_overlay").height( $(document).height() );\r
+               },\r
+               \r
+               _maintainPosition: function(status) {\r
+                       if( $.alerts.repositionOnResize ) {\r
+                               switch(status) {\r
+                                       case true:\r
+                                               $(window).bind('resize', $.alerts._reposition);\r
+                                       break;\r
+                                       case false:\r
+                                               $(window).unbind('resize', $.alerts._reposition);\r
+                                       break;\r
+                               }\r
+                       }\r
+               }\r
+               \r
+       }\r
+       \r
+       // Shortuct functions\r
+       jAlert = function(message, title, callback) {\r
+               $.alerts.alert(message, title, callback);\r
+       }\r
+       \r
+       jConfirm = function(message, title, callback) {\r
+               $.alerts.confirm(message, title, callback);\r
+       };\r
+               \r
+       jPrompt = function(message, value, title, callback) {\r
+               $.alerts.prompt(message, value, title, callback);\r
+       };\r
+       \r
+})(jQuery);
\ No newline at end of file
index aaffc4b..2802ee2 100644 (file)
@@ -37,7 +37,7 @@
 
     // HTML template for the dropdowns
     dropdownTemplate = [
-      '<div class="dk_container" id="dk_container_{{ id }}" tabindex="{{ tabindex }}">',
+      '<div class="dk_container" id="dk_container_{{ id }}" tabindex="{{ tabindex }}" style="display:inline-block;">',
         '<a class="dk_toggle">',
           '<span class="dk_label">{{ label }}</span>',
         '</a>',
diff --git a/snf-astakos-app/astakos/im/static/im/js/jquery.uniform.js b/snf-astakos-app/astakos/im/static/im/js/jquery.uniform.js
new file mode 100644 (file)
index 0000000..f64214f
--- /dev/null
@@ -0,0 +1,672 @@
+/*
+
+Uniform v1.7.5
+Copyright © 2009 Josh Pyles / Pixelmatrix Design LLC
+http://pixelmatrixdesign.com
+
+Requires jQuery 1.4 or newer
+
+Much thanks to Thomas Reynolds and Buck Wilson for their help and advice on this
+
+Disabling text selection is made possible by Mathias Bynens <http://mathiasbynens.be/>
+and his noSelect plugin. <http://github.com/mathiasbynens/noSelect-jQuery-Plugin>
+
+Also, thanks to David Kaneda and Eugene Bond for their contributions to the plugin
+
+License:
+MIT License - http://www.opensource.org/licenses/mit-license.php
+
+Enjoy!
+
+*/
+
+(function($) {
+  $.uniform = {
+    options: {
+      selectClass:   'selector',
+      radioClass: 'radio',
+      checkboxClass: 'checker',
+      fileClass: 'uploader',
+      filenameClass: 'filename',
+      fileBtnClass: 'action',
+      fileDefaultText: 'No file selected',
+      fileBtnText: 'Choose File',
+      checkedClass: 'checked',
+      focusClass: 'focus',
+      disabledClass: 'disabled',
+      buttonClass: 'button',
+      activeClass: 'active',
+      hoverClass: 'hover',
+      useID: true,
+      idPrefix: 'uniform',
+      resetSelector: false,
+      autoHide: true
+    },
+    elements: []
+  };
+
+  if($.browser.msie && $.browser.version < 7){
+    $.support.selectOpacity = false;
+  }else{
+    $.support.selectOpacity = true;
+  }
+
+  $.fn.uniform = function(options) {
+
+    options = $.extend($.uniform.options, options);
+
+    var el = this;
+    //code for specifying a reset button
+    if(options.resetSelector != false){
+      $(options.resetSelector).mouseup(function(){
+        function resetThis(){
+          $.uniform.update(el);
+        }
+        setTimeout(resetThis, 10);
+      });
+    }
+    
+    function doInput(elem){
+      $el = $(elem);
+      $el.addClass($el.attr("type"));
+      storeElement(elem);
+    }
+    
+    function doTextarea(elem){
+      $(elem).addClass("uniform");
+      storeElement(elem);
+    }
+    
+    function doButton(elem){
+      var $el = $(elem);
+      
+      var divTag = $("<div>"),
+          spanTag = $("<span>");
+      
+      divTag.addClass(options.buttonClass);
+      
+      if(options.useID && $el.attr("id") != "") divTag.attr("id", options.idPrefix+"-"+$el.attr("id"));
+      
+      var btnText;
+      
+      if($el.is("a") || $el.is("button")){
+        btnText = $el.text();
+      }else if($el.is(":submit") || $el.is(":reset") || $el.is("input[type=button]")){
+        btnText = $el.attr("value");
+      }
+      
+      btnText = btnText == "" ? $el.is(":reset") ? "Reset" : "Submit" : btnText;
+      
+      spanTag.html(btnText);
+      
+      $el.css("opacity", 0);
+      $el.wrap(divTag);
+      $el.wrap(spanTag);
+      
+      //redefine variables
+      divTag = $el.closest("div");
+      spanTag = $el.closest("span");
+      
+      if($el.is(":disabled")) divTag.addClass(options.disabledClass);
+      
+      divTag.bind({
+        "mouseenter.uniform": function(){
+          divTag.addClass(options.hoverClass);
+        },
+        "mouseleave.uniform": function(){
+          divTag.removeClass(options.hoverClass);
+          divTag.removeClass(options.activeClass);
+        },
+        "mousedown.uniform touchbegin.uniform": function(){
+          divTag.addClass(options.activeClass);
+        },
+        "mouseup.uniform touchend.uniform": function(){
+          divTag.removeClass(options.activeClass);
+        },
+        "click.uniform touchend.uniform": function(e){
+          if($(e.target).is("span") || $(e.target).is("div")){    
+            if(elem[0].dispatchEvent){
+              var ev = document.createEvent('MouseEvents');
+              ev.initEvent( 'click', true, true );
+              elem[0].dispatchEvent(ev);
+            }else{
+              elem[0].click();
+            }
+          }
+        }
+      });
+      
+      elem.bind({
+        "focus.uniform": function(){
+          divTag.addClass(options.focusClass);
+        },
+        "blur.uniform": function(){
+          divTag.removeClass(options.focusClass);
+        }
+      });
+      
+      $.uniform.noSelect(divTag);
+      storeElement(elem);
+      
+    }
+
+    function doSelect(elem){
+      var $el = $(elem);
+      
+      var divTag = $('<div />'),
+          spanTag = $('<span />');
+      
+      if(!$el.css("display") == "none" && options.autoHide){
+        divTag.hide();
+      }
+
+      divTag.addClass(options.selectClass);
+
+      if(options.useID && elem.attr("id") != ""){
+        divTag.attr("id", options.idPrefix+"-"+elem.attr("id"));
+      }
+      
+      var selected = elem.find(":selected:first");
+      if(selected.length == 0){
+        selected = elem.find("option:first");
+      }
+      spanTag.html(selected.html());
+      
+      elem.css('opacity', 0);
+      elem.wrap(divTag);
+      elem.before(spanTag);
+
+      //redefine variables
+      divTag = elem.parent("div");
+      spanTag = elem.siblings("span");
+
+      elem.bind({
+        "change.uniform": function() {
+          spanTag.text(elem.find(":selected").html());
+          divTag.removeClass(options.activeClass);
+        },
+        "focus.uniform": function() {
+          divTag.addClass(options.focusClass);
+        },
+        "blur.uniform": function() {
+          divTag.removeClass(options.focusClass);
+          divTag.removeClass(options.activeClass);
+        },
+        "mousedown.uniform touchbegin.uniform": function() {
+          divTag.addClass(options.activeClass);
+        },
+        "mouseup.uniform touchend.uniform": function() {
+          divTag.removeClass(options.activeClass);
+        },
+        "click.uniform touchend.uniform": function(){
+          divTag.removeClass(options.activeClass);
+        },
+        "mouseenter.uniform": function() {
+          divTag.addClass(options.hoverClass);
+        },
+        "mouseleave.uniform": function() {
+          divTag.removeClass(options.hoverClass);
+          divTag.removeClass(options.activeClass);
+        },
+        "keyup.uniform": function(){
+          spanTag.text(elem.find(":selected").html());
+        }
+      });
+      
+      //handle disabled state
+      if($(elem).attr("disabled")){
+        //box is checked by default, check our box
+        divTag.addClass(options.disabledClass);
+      }
+      $.uniform.noSelect(spanTag);
+      
+      storeElement(elem);
+
+    }
+
+    function doCheckbox(elem){
+      var $el = $(elem);
+      
+      var divTag = $('<div />'),
+          spanTag = $('<span />');
+      
+      if(!$el.css("display") == "none" && options.autoHide){
+        divTag.hide();
+      }
+      
+      divTag.addClass(options.checkboxClass);
+
+      //assign the id of the element
+      if(options.useID && elem.attr("id") != ""){
+        divTag.attr("id", options.idPrefix+"-"+elem.attr("id"));
+      }
+
+      //wrap with the proper elements
+      $(elem).wrap(divTag);
+      $(elem).wrap(spanTag);
+
+      //redefine variables
+      spanTag = elem.parent();
+      divTag = spanTag.parent();
+
+      //hide normal input and add focus classes
+      $(elem)
+      .css("opacity", 0)
+      .bind({
+        "focus.uniform": function(){
+          divTag.addClass(options.focusClass);
+        },
+        "blur.uniform": function(){
+          divTag.removeClass(options.focusClass);
+        },
+        "click.uniform touchend.uniform": function(){
+          if(!$(elem).attr("checked")){
+            //box was just unchecked, uncheck span
+            spanTag.removeClass(options.checkedClass);
+          }else{
+            //box was just checked, check span.
+            spanTag.addClass(options.checkedClass);
+          }
+        },
+        "mousedown.uniform touchbegin.uniform": function() {
+          divTag.addClass(options.activeClass);
+        },
+        "mouseup.uniform touchend.uniform": function() {
+          divTag.removeClass(options.activeClass);
+        },
+        "mouseenter.uniform": function() {
+          divTag.addClass(options.hoverClass);
+        },
+        "mouseleave.uniform": function() {
+          divTag.removeClass(options.hoverClass);
+          divTag.removeClass(options.activeClass);
+        }
+      });
+      
+      //handle defaults
+      if($(elem).attr("checked")){
+        //box is checked by default, check our box
+        spanTag.addClass(options.checkedClass);
+      }
+
+      //handle disabled state
+      if($(elem).attr("disabled")){
+        //box is checked by default, check our box
+        divTag.addClass(options.disabledClass);
+      }
+
+      storeElement(elem);
+    }
+
+    function doRadio(elem){
+      var $el = $(elem);
+      
+      var divTag = $('<div />'),
+          spanTag = $('<span />');
+          
+      if(!$el.css("display") == "none" && options.autoHide){
+        divTag.hide();
+      }
+
+      divTag.addClass(options.radioClass);
+
+      if(options.useID && elem.attr("id") != ""){
+        divTag.attr("id", options.idPrefix+"-"+elem.attr("id"));
+      }
+
+      //wrap with the proper elements
+      $(elem).wrap(divTag);
+      $(elem).wrap(spanTag);
+
+      //redefine variables
+      spanTag = elem.parent();
+      divTag = spanTag.parent();
+
+      //hide normal input and add focus classes
+      $(elem)
+      .css("opacity", 0)
+      .bind({
+        "focus.uniform": function(){
+          divTag.addClass(options.focusClass);
+        },
+        "blur.uniform": function(){
+          divTag.removeClass(options.focusClass);
+        },
+        "click.uniform touchend.uniform": function(){
+          if(!$(elem).attr("checked")){
+            //box was just unchecked, uncheck span
+            spanTag.removeClass(options.checkedClass);
+          }else{
+            //box was just checked, check span
+            var classes = options.radioClass.split(" ")[0];
+            $("." + classes + " span." + options.checkedClass + ":has([name='" + $(elem).attr('name') + "'])").removeClass(options.checkedClass);
+            spanTag.addClass(options.checkedClass);
+          }
+        },
+        "mousedown.uniform touchend.uniform": function() {
+          if(!$(elem).is(":disabled")){
+            divTag.addClass(options.activeClass);
+          }
+        },
+        "mouseup.uniform touchbegin.uniform": function() {
+          divTag.removeClass(options.activeClass);
+        },
+        "mouseenter.uniform touchend.uniform": function() {
+          divTag.addClass(options.hoverClass);
+        },
+        "mouseleave.uniform": function() {
+          divTag.removeClass(options.hoverClass);
+          divTag.removeClass(options.activeClass);
+        }
+      });
+
+      //handle defaults
+      if($(elem).attr("checked")){
+        //box is checked by default, check span
+        spanTag.addClass(options.checkedClass);
+      }
+      //handle disabled state
+      if($(elem).attr("disabled")){
+        //box is checked by default, check our box
+        divTag.addClass(options.disabledClass);
+      }
+
+      storeElement(elem);
+
+    }
+
+    function doFile(elem){
+      //sanitize input
+      var $el = $(elem);
+
+      var divTag = $('<div />'),
+          filenameTag = $('<span>'+options.fileDefaultText+'</span>'),
+          btnTag = $('<span>'+options.fileBtnText+'</span>');
+      
+      if(!$el.css("display") == "none" && options.autoHide){
+        divTag.hide();
+      }
+
+      divTag.addClass(options.fileClass);
+      filenameTag.addClass(options.filenameClass);
+      btnTag.addClass(options.fileBtnClass);
+
+      if(options.useID && $el.attr("id") != ""){
+        divTag.attr("id", options.idPrefix+"-"+$el.attr("id"));
+      }
+
+      //wrap with the proper elements
+      $el.wrap(divTag);
+      $el.after(btnTag);
+      $el.after(filenameTag);
+
+      //redefine variables
+      divTag = $el.closest("div");
+      filenameTag = $el.siblings("."+options.filenameClass);
+      btnTag = $el.siblings("."+options.fileBtnClass);
+
+      //set the size
+      if(!$el.attr("size")){
+        var divWidth = divTag.width();
+        //$el.css("width", divWidth);
+        $el.attr("size", divWidth/10);
+      }
+
+      //actions
+      var setFilename = function()
+      {
+        var filename = $el.val();
+        if (filename === '')
+        {
+          filename = options.fileDefaultText;
+        }
+        else
+        {
+          filename = filename.split(/[\/\\]+/);
+          filename = filename[(filename.length-1)];
+        }
+        filenameTag.text(filename);
+      };
+
+      // Account for input saved across refreshes
+      setFilename();
+
+      $el
+      .css("opacity", 0)
+      .bind({
+        "focus.uniform": function(){
+          divTag.addClass(options.focusClass);
+        },
+        "blur.uniform": function(){
+          divTag.removeClass(options.focusClass);
+        },
+        "mousedown.uniform": function() {
+          if(!$(elem).is(":disabled")){
+            divTag.addClass(options.activeClass);
+          }
+        },
+        "mouseup.uniform": function() {
+          divTag.removeClass(options.activeClass);
+        },
+        "mouseenter.uniform": function() {
+          divTag.addClass(options.hoverClass);
+        },
+        "mouseleave.uniform": function() {
+          divTag.removeClass(options.hoverClass);
+          divTag.removeClass(options.activeClass);
+        }
+      });
+
+      // IE7 doesn't fire onChange until blur or second fire.
+      if ($.browser.msie){
+        // IE considers browser chrome blocking I/O, so it
+        // suspends tiemouts until after the file has been selected.
+        $el.bind('click.uniform.ie7', function() {
+          setTimeout(setFilename, 0);
+        });
+      }else{
+        // All other browsers behave properly
+        $el.bind('change.uniform', setFilename);
+      }
+
+      //handle defaults
+      if($el.attr("disabled")){
+        //box is checked by default, check our box
+        divTag.addClass(options.disabledClass);
+      }
+      
+      $.uniform.noSelect(filenameTag);
+      $.uniform.noSelect(btnTag);
+      
+      storeElement(elem);
+
+    }
+    
+    $.uniform.restore = function(elem){
+      if(elem == undefined){
+        elem = $($.uniform.elements);
+      }
+      
+      $(elem).each(function(){
+        if($(this).is(":checkbox")){
+          //unwrap from span and div
+          $(this).unwrap().unwrap();
+        }else if($(this).is("select")){
+          //remove sibling span
+          $(this).siblings("span").remove();
+          //unwrap parent div
+          $(this).unwrap();
+        }else if($(this).is(":radio")){
+          //unwrap from span and div
+          $(this).unwrap().unwrap();
+        }else if($(this).is(":file")){
+          //remove sibling spans
+          $(this).siblings("span").remove();
+          //unwrap parent div
+          $(this).unwrap();
+        }else if($(this).is("button, :submit, :reset, a, input[type='button']")){
+          //unwrap from span and div
+          $(this).unwrap().unwrap();
+        }
+        
+        //unbind events
+        $(this).unbind(".uniform");
+        
+        //reset inline style
+        $(this).css("opacity", "1");
+        
+        //remove item from list of uniformed elements
+        var index = $.inArray($(elem), $.uniform.elements);
+        $.uniform.elements.splice(index, 1);
+      });
+    };
+
+    function storeElement(elem){
+      //store this element in our global array
+      elem = $(elem).get();
+      if(elem.length > 1){
+        $.each(elem, function(i, val){
+          $.uniform.elements.push(val);
+        });
+      }else{
+        $.uniform.elements.push(elem);
+      }
+    }
+    
+    //noSelect v1.0
+    $.uniform.noSelect = function(elem) {
+      function f() {
+       return false;
+      };
+      $(elem).each(function() {
+       this.onselectstart = this.ondragstart = f; // Webkit & IE
+       $(this)
+        .mousedown(f) // Webkit & Opera
+        .css({ MozUserSelect: 'none' }); // Firefox
+      });
+     };
+
+    $.uniform.update = function(elem){
+      if(elem == undefined){
+        elem = $($.uniform.elements);
+      }
+      //sanitize input
+      elem = $(elem);
+
+      elem.each(function(){
+        //do to each item in the selector
+        //function to reset all classes
+        var $e = $(this);
+
+        if($e.is("select")){
+          //element is a select
+          var spanTag = $e.siblings("span");
+          var divTag = $e.parent("div");
+
+          divTag.removeClass(options.hoverClass+" "+options.focusClass+" "+options.activeClass);
+
+          //reset current selected text
+          spanTag.html($e.find(":selected").html());
+
+          if($e.is(":disabled")){
+            divTag.addClass(options.disabledClass);
+          }else{
+            divTag.removeClass(options.disabledClass);
+          }
+
+        }else if($e.is(":checkbox")){
+          //element is a checkbox
+          var spanTag = $e.closest("span");
+          var divTag = $e.closest("div");
+
+          divTag.removeClass(options.hoverClass+" "+options.focusClass+" "+options.activeClass);
+          spanTag.removeClass(options.checkedClass);
+
+          if($e.is(":checked")){
+            spanTag.addClass(options.checkedClass);
+          }
+          if($e.is(":disabled")){
+            divTag.addClass(options.disabledClass);
+          }else{
+            divTag.removeClass(options.disabledClass);
+          }
+
+        }else if($e.is(":radio")){
+          //element is a radio
+          var spanTag = $e.closest("span");
+          var divTag = $e.closest("div");
+
+          divTag.removeClass(options.hoverClass+" "+options.focusClass+" "+options.activeClass);
+          spanTag.removeClass(options.checkedClass);
+
+          if($e.is(":checked")){
+            spanTag.addClass(options.checkedClass);
+          }
+
+          if($e.is(":disabled")){
+            divTag.addClass(options.disabledClass);
+          }else{
+            divTag.removeClass(options.disabledClass);
+          }
+        }else if($e.is(":file")){
+          var divTag = $e.parent("div");
+          var filenameTag = $e.siblings(options.filenameClass);
+          btnTag = $e.siblings(options.fileBtnClass);
+
+          divTag.removeClass(options.hoverClass+" "+options.focusClass+" "+options.activeClass);
+
+          filenameTag.text($e.val());
+
+          if($e.is(":disabled")){
+            divTag.addClass(options.disabledClass);
+          }else{
+            divTag.removeClass(options.disabledClass);
+          }
+        }else if($e.is(":submit") || $e.is(":reset") || $e.is("button") || $e.is("a") || elem.is("input[type=button]")){
+          var divTag = $e.closest("div");
+          divTag.removeClass(options.hoverClass+" "+options.focusClass+" "+options.activeClass);
+          
+          if($e.is(":disabled")){
+            divTag.addClass(options.disabledClass);
+          }else{
+            divTag.removeClass(options.disabledClass);
+          }
+          
+        }
+        
+      });
+    };
+
+    return this.each(function() {
+      if($.support.selectOpacity){
+        var elem = $(this);
+
+        if(elem.is("select")){
+          //element is a select
+          if(elem.attr("multiple") != true){
+            //element is not a multi-select
+            if(elem.attr("size") == undefined || elem.attr("size") <= 1){
+              doSelect(elem);
+            }
+          }
+        }else if(elem.is(":checkbox")){
+          //element is a checkbox
+          doCheckbox(elem);
+        }else if(elem.is(":radio")){
+          //element is a radio
+          doRadio(elem);
+        }else if(elem.is(":file")){
+          //element is a file upload
+          doFile(elem);
+        }else if(elem.is(":text, :password, input[type='email']")){
+          doInput(elem);
+        }else if(elem.is("textarea")){
+          doTextarea(elem);
+        }else if(elem.is("a") || elem.is(":submit") || elem.is(":reset") || elem.is("button") || elem.is("input[type=button]")){
+          doButton(elem);
+        }
+          
+      }
+    });
+  };
+})(jQuery);
\ No newline at end of file
diff --git a/snf-astakos-app/astakos/im/static/im/js/quotas.js b/snf-astakos-app/astakos/im/static/im/js/quotas.js
new file mode 100644 (file)
index 0000000..1b7bf1e
--- /dev/null
@@ -0,0 +1,301 @@
+function group_form_show_resources(el){
+       
+       el.addClass('selected');
+       var id = el.attr('id');
+       $('.quotas-form .group').each(function() {
+               if( $(this).hasClass(id) ) {
+                       $(this).appendTo('.visible');
+                       $(this).show('slow');
+                       $(this).find('input')[0].focus()
+               }
+       });
+       if ($('.quotas-form .with-info .with-errors input[type="text"]')){
+               $(this)[0].focus();     
+       }
+
+}
+
+
+function bytesToSize2(bytes) {
+    var sizes = [ 'n/a', 'bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
+    var i = +Math.floor(Math.log(bytes) / Math.log(1024));
+    return  (bytes / Math.pow(1024, i)).toFixed( 0 ) + sizes[ isNaN( bytes ) ? 0 : i+1 ];
+}
+
+$(document).ready(function() {
+
+       
+       
+       // ugly fix to transfer data easily 
+       $('.with-info input[name^="is_selected_"]').each(function() {
+               $(this).parents('.form-row').hide();
+       });
+    
+       $('.quotas-form ul li a').click(function(e){
+               
+               // check the hidden input field
+               $(this).siblings('input[type="hidden"]').attr('checked','checked');
+               
+               // get the hidden input field without the proxy
+               // and check the python form field
+               hidden_name = $(this).siblings('input[type="hidden"]').attr('name').replace("proxy_","");
+               $("input[name='"+hidden_name+"']").attr('checked','checked');  
+               
+               // prevent extra actions if it is checked                
+               if ( $(this).hasClass('selected')){
+                       e.preventDefault();
+               } else {
+                       
+                       // show the relevant fieldsets
+                       group_form_show_resources($(this));
+               }   
+       });
+       
+        
+       
+       
+        
+       
+       $('.quotas-form .group .delete').click(function(e){
+               
+               e.preventDefault(); 
+               
+               // clear form fields
+               $(this).siblings('fieldset').find('input').val('');
+               
+               // clear errors
+               $(this).siblings('fieldset').find('.form-row').removeClass('with-errors');
+                
+               // hide relevant fieldset 
+               $(this).parents('.group').hide('slow', function() {
+                   $(this).appendTo('.not-visible');   
+               });
+               
+               group_class = $(this).parents('.group').attr('class').replace('group ', '');
+               
+               // unselect group icon
+               $('.quotas-form ul li a').each(function() {
+                       if($(this).attr('id')==group_class) {
+                               $(this).removeClass('selected');
+                               $(this).siblings('input[type="hidden"]').removeAttr('checked');
+                               
+                               // get the hidden input field without the proxy
+                               // and check the python form field
+                               hidden_name = $(this).siblings('input[type="hidden"]').attr('name').replace("proxy_","");
+                               $("input[name='"+hidden_name+"']").removeAttr('checked');  
+                               
+                       }
+               }); 
+               
+               // clear hidden fields
+               $(this).siblings('fieldset').find('input[type="text"]').each(function() {
+                       hidden_name = $(this).attr('name').replace("_proxy","");
+                       hidden_input = $("input[name='"+hidden_name+"']");
+                       hidden_input.val('');
+               });
+                
+                
+       });
+                
+       // if you fill _proxy fields do stuff 
+       $('.quotas-form .quota input[type="text"]').change(function () {
+               
+               
+                
+               // get value from input
+               var value = $(this).val();
+               
+               //get input name without _proxy
+               hidden_name = $(this).attr('name').replace("_proxy","");
+               var hidden_input = $("input[name='"+hidden_name+"']");
+               
+               if (value) {
+                       // actions for humanize fields
+                       if ($(this).hasClass('dehumanize')){
+                               
+                               var flag = 0;
+
+                               // check if the value is not float
+                               var num_float = parseFloat(value);
+                               num_float= String(num_float);
+
+                               if (num_float.indexOf(".") == 1){
+                                       flag = 1 ; 
+                                       msg="Please enter an integer";
+                               } else {
+                                       var num = parseInt(value);
+                                       if ( num == '0' ) { 
+                                               flag = 1 ; msg="zero"
+                                       } else {
+                                               if ( value && !num ) { flag = 1 ; msg="Invalid format"}
+                                       
+                                               var bytes = num;
+                                               
+                                               // remove any numbers and get suffix                            
+                                               var suffix = value.replace( num, '');
+               
+                                                // validate suffix. 'i' renders it case insensitive
+                                               var suf = suffix.match( new RegExp('^(GB|KB|MB|TB|bytes|G|K|M|T|byte)$', 'i'));
+                                               if (suf){
+                                                       
+                                                       suf = suf[0].toLowerCase(); 
+                                                       suf = suf.substr(0,1);
+                                               
+                                                       // transform to bytes
+                                                       switch (suf){
+                                                               case 'b': 
+                                                                 bytes = num*Math.pow(1024,0);
+                                                                 break;
+                                                               case 'k':
+                                                                 bytes = num*Math.pow(1024,1);
+                                                                 break;
+                                                               case 'm':
+                                                                 bytes = num*Math.pow(1024,2);
+                                                                 break;
+                                                               case 'g':
+                                                                 bytes = num*Math.pow(1024,3);
+                                                                 break;
+                                                               case 't':
+                                                                 bytes = num*Math.pow(1024,4);
+                                                                 break;    
+                                                               default:
+                                                                 bytes = num; 
+                                                       }
+                                               } else {
+                                                       if (num) {
+                                                               flag = 1;
+                                                               msg ="You must specify correct units" 
+                                                       }  
+                                                        
+                                               }
+                                       }
+                                       
+                                       
+                                       
+                               }
+                               
+                                
+                               
+                               
+                               if ( flag == '1' ){ 
+                                       $(this).parents('.form-row').addClass('with-errors');
+                                       $(this).parents('.form-row').find('.error-msg').html(msg);
+                                       bytes = value;
+                                       $(this).focus();
+                                       
+                                        
+                               } else {
+                                       $(this).parents('.form-row').removeClass('with-errors');
+                               }
+                               
+                               hidden_input.val(bytes);
+                               
+                               
+                       }
+                        
+                       // validation actions for int fields
+                       else {
+       
+                               var is_int = value.match (new RegExp('^[0-9]*$'));
+                               if ( !is_int ){ 
+                                       $(this).parents('.form-row').find('.error-msg').html('Enter a positive integer');
+                                       $(this).parents('.form-row').addClass('with-errors');
+                                        
+                               } else {
+                                       if ( value == '0'){
+                                               $(this).parents('.form-row').find('.error-msg').html('Ensure this value is greater than or equal to 1');
+                                               $(this).parents('.form-row').addClass('with-errors');
+                                       }else {
+                                               $(this).parents('.form-row').removeClass('with-errors');
+                                       }
+                                       
+                                       
+                               }
+                               hidden_input.val(value);
+       
+                       }
+               
+               } else {
+                       hidden_input.removeAttr('value');
+               }
+               $('#icons span.info').removeClass('error-msg');
+               
+        });
+        
+       
+       // if hidden checkboxes are checked, the right group is selected 
+       $('.with-info input[name^="is_selected_"]').each(function() {
+               if ($(this).attr('checked')){
+                       
+                       // get hidden input name
+                       hidden_name = $(this).attr('name');
+                       $("input[name='proxy_"+hidden_name+"']").attr('checked','checked'); 
+                       
+                       // pretend to check the ul li a
+                       // show the relevant fieldsets
+                       var mock_a = $("input[name='proxy_"+hidden_name+"']").siblings('a');
+                       group_form_show_resources(mock_a);
+                        
+               }
+       }); 
+       
+       
+       
+       // if input_uplimit fields are filled,
+       // fill the _uplimit_proxy ones
+        
+       $('.with-info input[name$="_uplimit"]').each(function() {
+               if ($(this).val()){
+                       
+                       // get value from input
+                       var value = $(this).val();
+                       
+                       
+                       // get hidden input name
+                       hidden_name = $(this).attr('name');
+                       var field = $("input[name='"+hidden_name+"_proxy']"); 
+                       
+                       
+                       if ( (field.hasClass('dehumanize')) && !($(this).parents('.form-row').hasClass('with-errors'))) {
+                               // for dehumanize fields transform bytes to KB, MB, etc
+                               // unless there is an error
+                               field.val(bytesToSize2(value))
+                       } else {
+                               // else just return the value
+                               field.val(value);       
+                       }
+                       
+                       var group_class = field.parents('div[class^="group"]').attr('class').replace('group ', '');
+                       
+                        
+                        
+                       
+                       // select group icon
+                       $('.quotas-form ul li a').each(function() {
+                               
+                               if($(this).attr('id') == group_class) {
+                                       $(this).addClass('selected');
+                                       $(this).siblings('input[type="hidden"]').attr('checked', 'checked');
+                                       
+                                       // get the hidden input field without the proxy
+                                       // and check the python form field
+                                       hidden_name = $(this).siblings('input[type="hidden"]').attr('name').replace("proxy_","");
+                                       $("input[name='"+hidden_name+"']").attr('checked', 'checked');  
+                                       
+                                       group_form_show_resources($(this));
+                                       
+                               }
+                       }); 
+                       
+               
+                       
+                       // if the field has class error, transfer error to the proxy fields
+                       if ( $(this).parents('.form-row').hasClass('with-errors') ) {
+                               field.parents('.form-row').addClass('with-errors');
+                       }
+                       
+                        
+               }
+       }); 
+       
+});
\ No newline at end of file
index 647d5ae..21a4ea5 100644 (file)
@@ -37,12 +37,14 @@ Django settings metadata. To be used in setup.py snf-webproject entry points.
 """
 
 installed_apps = [
-        {'before': 'django.contrib.admin',
-         'insert': 'astakos.im',},
-        'django.contrib.auth',
-        'django.contrib.contenttypes',
-        'django.contrib.sessions',
-        'django.contrib.messages'
+    {'before': 'django.contrib.admin',
+     'insert': 'astakos.im', },
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+#    'djcelery',
+#    'debug_toolbar',
 ]
 
 context_processors = [
@@ -56,6 +58,7 @@ context_processors = [
     'astakos.im.context_processors.invitations',
     'astakos.im.context_processors.menu',
     'astakos.im.context_processors.custom_messages',
+    'astakos.im.context_processors.group_kinds',
     'synnefo.lib.context_processors.cloudbar'
 ]
 
@@ -64,22 +67,31 @@ middlware_classes = [
     'astakos.im.middleware.CookieAuthenticationMiddleware',
     'synnefo.lib.middleware.LoggingConfigMiddleware',
     'synnefo.lib.middleware.SecureMiddleware',
-    'django.middleware.csrf.CsrfViewMiddleware'
+    'django.middleware.csrf.CsrfViewMiddleware',
+#    'debug_toolbar.middleware.DebugToolbarMiddleware',
 ]
 
 loggers = {
-        'astakos': {
-            'handlers': ['console'],
-            'level': 'INFO'
-        }
+    'astakos': {
+        'handlers': ['console'],
+        'level': 'INFO'
+    }
 }
 
 static_files = {'astakos.im': ''}
 
 # The following settings will replace the default django settings
 AUTHENTICATION_BACKENDS = ('astakos.im.auth_backends.EmailBackend',
-                            'astakos.im.auth_backends.TokenBackend')
+                           'astakos.im.auth_backends.TokenBackend')
 LOGIN_URL = '/im'
 
 CUSTOM_USER_MODEL = 'astakos.im.AstakosUser'
 
+#SOUTH_TESTS_MIGRATE = False
+
+import djcelery
+djcelery.setup_loader()
+
+BROKER_URL = ''
+
+# INTERNAL_IPS = ('127.0.0.1',)
\ No newline at end of file
index de0f36c..aba41a0 100644 (file)
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
-from django.http import HttpResponseBadRequest, HttpResponseRedirect
+from django.http import HttpResponseRedirect
 from django.shortcuts import render_to_response
 from django.template import RequestContext
-from django.contrib.auth import authenticate
 from django.contrib import messages
 from django.utils.translation import ugettext as _
 from django.views.decorators.csrf import csrf_exempt
@@ -44,15 +43,18 @@ from django.contrib.auth.decorators import login_required
 
 from astakos.im.util import prepare_response, get_query
 from astakos.im.views import requires_anonymous, signed_terms_required
-from astakos.im.models import AstakosUser, PendingThirdPartyUser
+from astakos.im.models import PendingThirdPartyUser
 from astakos.im.forms import LoginForm, ExtendedPasswordChangeForm
 from astakos.im.settings import RATELIMIT_RETRIES_ALLOWED
 from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION
 
+import astakos.im.messages as astakos_messages
+
 from ratelimit.decorators import ratelimit
 
-retries = RATELIMIT_RETRIES_ALLOWED-1
-rate = str(retries)+'/m'
+retries = RATELIMIT_RETRIES_ALLOWED - 1
+rate = str(retries) + '/m'
+
 
 @require_http_methods(["GET", "POST"])
 @csrf_exempt
@@ -63,7 +65,9 @@ def login(request, on_failure='im/login.html'):
     on_failure: the template name to render on login failure
     """
     was_limited = getattr(request, 'limited', False)
-    form = LoginForm(data=request.POST, was_limited=was_limited, request=request)
+    form = LoginForm(data=request.POST,
+                     was_limited=was_limited,
+                     request=request)
     next = get_query(request).get('next', '')
     username = get_query(request).get('key')
     
@@ -77,21 +81,18 @@ def login(request, on_failure='im/login.html'):
         )
     # get the user from the cash
     user = form.user_cache
-    
+
     message = None
     if not user:
-        message = _('Cannot authenticate account')
+        message = _(astakos_messages.ACCOUNT_AUTHENTICATION_FAILED)
     elif not user.is_active:
         if not user.activation_sent:
-            message = _('Your request is pending activation')
+            message = _(astakos_messages.ACCOUNT_PENDING_ACTIVATION)
         else:
-            url = reverse('send_activation', kwargs={'user_id':user.id})
-            message = _('You have not followed the activation link. \
-            <a href="%s">Resend activation email?</a>' % url)
+            send_activation_url = reverse('send_activation', kwargs={'user_id':user.id})
+            message = _(astakos_messages.ACCOUNT_RESEND_ACTIVATION) % locals()
     elif user.provider not in ('local', ''):
-        message = _(
-            'Local login is not the current authentication method for this account.'
-        )
+        message = _(astakos_messages.NO_LOCAL_AUTH)
     
     if message:
         messages.error(request, message)
@@ -107,7 +108,7 @@ def login(request, on_failure='im/login.html'):
         except:
             messages.error(
                 request,
-                _('Account failed to switch to %(provider)s' % locals())
+                _(astakos_messages.SWITCH_ACCOUNT_FAILURE)
             )
             return render_to_response(
                 on_failure,
@@ -122,7 +123,7 @@ def login(request, on_failure='im/login.html'):
             new.delete()
             messages.success(
                 request,
-                _('Account successfully switched to %(provider)s' % user.__dict__)
+                _(astakos_messages.SWITCH_ACCOUNT_SUCCESS_WITH_PROVIDER) % user.__dict__
             )
     return prepare_response(request, user, next)
 
@@ -146,4 +147,4 @@ def password_change(request, template_name='registration/password_change_form.ht
         form = password_change_form(user=request.user)
     return render_to_response(template_name, {
         'form': form,
-    }, context_instance=RequestContext(request))
\ No newline at end of file
+    }, context_instance=RequestContext(request))
index b9d1b58..40e45cb 100644 (file)
@@ -32,9 +32,7 @@
 # or implied, of GRNET S.A.
 
 from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
 from django.utils.translation import ugettext as _
-from django.contrib import messages
 from django.utils.http import urlencode
 from django.contrib.auth import authenticate
 from django.http import (
@@ -43,17 +41,19 @@ from django.http import (
 from django.core.exceptions import ValidationError
 from django.views.decorators.http import require_http_methods
 
-from urllib import quote
-from urlparse import urlunsplit, urlsplit, urlparse, parse_qsl
+from urlparse import urlunsplit, urlsplit, parse_qsl
 
 from astakos.im.settings import COOKIE_DOMAIN
 from astakos.im.util import restrict_next
 from astakos.im.functions import login as auth_login, logout
 
+import astakos.im.messages as astakos_messages
+
 import logging
 
 logger = logging.getLogger(__name__)
 
+
 @require_http_methods(["GET", "POST"])
 def login(request):
     """
@@ -66,11 +66,11 @@ def login(request):
     """
     next = request.GET.get('next')
     if not next:
-        return HttpResponseBadRequest(_('No next parameter'))
+        return HttpResponseBadRequest(_(astakos_messages.MISSING_NEXT_PARAMETER))
     if not restrict_next(
         next, domain=COOKIE_DOMAIN, allowed_schemes=('pithos',)
     ):
-        return HttpResponseForbidden(_('Not allowed next parameter'))
+        return HttpResponseForbidden(_(astakos_messages.NOT_ALLOWED_NEXT_PARAM))
     force = request.GET.get('force', None)
     response = HttpResponse()
     if force == '':
@@ -78,17 +78,17 @@ def login(request):
     if request.user.is_authenticated():
         # if user has not signed the approval terms
         # redirect to approval terms with next the request path
-        if not request.user.signed_terms():
+        if not request.user.signed_terms:
             # first build next parameter
             parts = list(urlsplit(request.build_absolute_uri()))
             params = dict(parse_qsl(parts[3], keep_blank_values=True))
             # delete force parameter
             parts[3] = urlencode(params)
             next = urlunsplit(parts)
-            
+
             # build url location
             parts[2] = reverse('latest_terms')
-            params = {'next':next}
+            params = {'next': next}
             parts[3] = urlencode(params)
             url = urlunsplit(parts)
             response['Location'] = url
@@ -105,18 +105,23 @@ def login(request):
             except ValidationError, e:
                 return HttpResponseBadRequest(e)
             # authenticate before login
-            user = authenticate(email=request.user.email, auth_token=request.user.auth_token)
+            user = authenticate(email=request.user.email,
+                                auth_token=request.user.auth_token
+                                )
             auth_login(request, user)
             logger.info('Token reset for %s' % request.user.email)
         parts = list(urlsplit(next))
-        parts[3] = urlencode({'user': request.user.email, 'token': request.user.auth_token})
+        parts[3] = urlencode({'user': request.user.email,
+                              'token': request.user.auth_token
+                              }
+                             )
         url = urlunsplit(parts)
         response['Location'] = url
         response.status_code = 302
         return response
     else:
         # redirect to login with next the request path
-        
+
         # first build next parameter
         parts = list(urlsplit(request.build_absolute_uri()))
         params = dict(parse_qsl(parts[3], keep_blank_values=True))
@@ -125,12 +130,12 @@ def login(request):
             del params['force']
         parts[3] = urlencode(params)
         next = urlunsplit(parts)
-        
+
         # build url location
-        parts[2] = reverse('astakos.im.views.index')
-        params = {'next':next}
+        parts[2] = reverse('index')
+        params = {'next': next}
         parts[3] = urlencode(params)
         url = urlunsplit(parts)
         response['Location'] = url
         response.status_code = 302
-        return response
\ No newline at end of file
+        return response
index 02adb95..4f9f284 100644 (file)
@@ -1,18 +1,18 @@
 # Copyright 2011-2012 GRNET S.A. All rights reserved.
-# 
+#
 # Redistribution and use in source and binary forms, with or
 # without modification, are permitted provided that the following
 # conditions are met:
-# 
+#
 #   1. Redistributions of source code must retain the above
 #      copyright notice, this list of conditions and the following
 #      disclaimer.
-# 
+#
 #   2. Redistributions in binary form must reproduce the above
 #      copyright notice, this list of conditions and the following
 #      disclaimer in the documentation and/or other materials
 #      provided with the distribution.
-# 
+#
 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
@@ -25,7 +25,7 @@
 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 # POSSIBILITY OF SUCH DAMAGE.
-# 
+#
 # The views and conclusions contained in the software and
 # documentation are those of the authors and should not be
 # interpreted as representing official policies, either expressed
@@ -36,20 +36,18 @@ from django.utils.translation import ugettext as _
 from django.contrib import messages
 from django.template import RequestContext
 from django.views.decorators.http import require_http_methods
-from django.db.models import Q
-from django.core.exceptions import ValidationError
 from django.http import HttpResponseRedirect
 from django.core.urlresolvers import reverse
-from urlparse import urlunsplit, urlsplit
-from django.utils.http import urlencode
+from django.core.exceptions import ImproperlyConfigured
 
-from astakos.im.util import prepare_response, get_context, get_invitation
+from astakos.im.util import prepare_response, get_context
 from astakos.im.views import requires_anonymous, render_response
-from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION, BASEURL
-
+from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION
 from astakos.im.models import AstakosUser, PendingThirdPartyUser
 from astakos.im.forms import LoginForm
-from astakos.im.activation_backends import get_backend, SimpleBackend
+from astakos.im.activation_backends import get_backend
+
+import astakos.im.messages as astakos_messages
 
 import logging
 
@@ -57,7 +55,7 @@ logger = logging.getLogger(__name__)
 
 class Tokens:
     # these are mapped by the Shibboleth SP software
-    SHIB_EPPN = "HTTP_EPPN" # eduPersonPrincipalName
+    SHIB_EPPN = "HTTP_EPPN"  # eduPersonPrincipalName
     SHIB_NAME = "HTTP_SHIB_INETORGPERSON_GIVENNAME"
     SHIB_SURNAME = "HTTP_SHIB_PERSON_SURNAME"
     SHIB_CN = "HTTP_SHIB_PERSON_COMMONNAME"
@@ -66,6 +64,7 @@ class Tokens:
     SHIB_SESSION_ID = "HTTP_SHIB_SESSION_ID"
     SHIB_MAIL = "HTTP_SHIB_MAIL"
 
+
 @require_http_methods(["GET", "POST"])
 @requires_anonymous
 def login(
@@ -77,11 +76,11 @@ def login(
     extra_context = extra_context or {}
 
     tokens = request.META
-    
+
     try:
         eppn = tokens.get(Tokens.SHIB_EPPN)
         if not eppn:
-            raise KeyError(_('Missing unique token in request'))
+            raise KeyError(_(astakos_messages.SHIBBOLETH_MISSING_EPPN))
         if Tokens.SHIB_DISPLAYNAME in tokens:
             realname = tokens[Tokens.SHIB_DISPLAYNAME]
         elif Tokens.SHIB_CN in tokens:
@@ -89,7 +88,7 @@ def login(
         elif Tokens.SHIB_NAME in tokens and Tokens.SHIB_SURNAME in tokens:
             realname = tokens[Tokens.SHIB_NAME] + ' ' + tokens[Tokens.SHIB_SURNAME]
         else:
-            raise KeyError(_('Missing user name in request'))
+            raise KeyError(_(astakos_messages.SHIBBOLETH_MISSING_NAME))
     except KeyError, e:
         extra_context['login_form'] = LoginForm(request=request)
         messages.error(request, e)
@@ -112,23 +111,19 @@ def login(
                                     request.GET.get('next'),
                                     'renew' in request.GET)
         elif not user.activation_sent:
-            message = _('Your request is pending activation')
+            message = _(astakos_messages.ACCOUNT_PENDING_ACTIVATION)
             messages.error(request, message)
         else:
             urls = {}
-            urls['send_activation'] = reverse(
+            urls['send_activation_url'] = reverse(
                 'send_activation',
                 kwargs={'user_id':user.id}
             )
-            urls['signup'] = reverse(
+            urls['signup_url'] = reverse(
                 'shibboleth_signup',
                 args= [user.username]
             )   
-            message = _(
-                'You have not followed the activation link. \
-                <a href="%(send_activation)s">Resend activation email?</a> or \
-                <a href="%(signup)s">Provide new email?</a>' % urls
-            )
+            message = _(astakos_messages.INACTIVE_ACCOUNT_CHANGE_EMAIL) % urls
             messages.error(request, message)
         return render_response(login_template,
                                login_form = LoginForm(request=request),
@@ -150,7 +145,7 @@ def login(
             logger.exception(e)
             template = login_template
             extra_context['login_form'] = LoginForm(request=request)
-            messages.error(request, _('Something went wrong.'))
+            messages.error(request, _(astakos_messages.GENERIC_ERROR))
         else:
             if not ENABLE_LOCAL_ACCOUNT_MIGRATION:
                 url = reverse(
@@ -179,14 +174,14 @@ def signup(
 ):
     extra_context = extra_context or {}
     if not username:
-        return HttpResponseBadRequest(_('Missing key parameter.'))
+        return HttpResponseBadRequest(_(astakos_messages.MISSING_KEY_PARAMETER))
     try:
         pending = PendingThirdPartyUser.objects.get(username=username)
     except PendingThirdPartyUser.DoesNotExist:
         try:
             user = AstakosUser.objects.get(username=username)
         except AstakosUser.DoesNotExist:
-            return HttpResponseBadRequest(_('Invalid key.'))
+            return HttpResponseBadRequest(_(astakos_messages.INVALID_KEY_PARAMETER))
     else:
         d = pending.__dict__
         d.pop('_state', None)
@@ -205,4 +200,4 @@ def signup(
     return render_response(
             on_creation_template,
             context_instance=get_context(request, extra_context)
-    )
\ No newline at end of file
+    )
diff --git a/snf-astakos-app/astakos/im/tasks.py b/snf-astakos-app/astakos/im/tasks.py
new file mode 100644 (file)
index 0000000..954c445
--- /dev/null
@@ -0,0 +1,78 @@
+# Copyright 2011 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from celery.task import task, periodic_task
+from celery.schedules import crontab
+
+from functools import wraps
+
+from astakos.im.endpoints.qh import send_quota
+from astakos.im.endpoints.aquarium.producer import (report_credits_event,
+                                                    report_user_event
+                                                    )
+from astakos.im.endpoints.aquarium.client import AquariumClient
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+def log(func):
+    @wraps(func)
+    def wrapper(*args, **kwargs):
+        logger.info('Starting the %s with args=%s kwargs=%s' % (
+                    func, args, kwargs
+                    )
+                    )
+        return func(*args, **kwargs)
+    return wrapper
+
+
+@periodic_task(run_every=crontab(day_of_month='1'))
+@log
+def propagate_credits_update():
+    report_credits_event()
+
+
+@task
+@log
+def propagate_groupmembers_quota(group):
+    if group.is_disabled:
+        return
+    send_quota(group.approved_members)
+
+
+@task
+@log
+def request_billing(user, start, end):
+    return AquariumClient().get_billing(user, start, end)
index f75af97..3a4c87d 100644 (file)
@@ -7,7 +7,7 @@
 
 {% block page.quicknav.items %}
         <li class="{% block signup_class %}{% endblock %}">
-            <a href="{% url astakos.im.views.logout %}">LOGOUT</a>
+            <a href="{% url logout %}">LOGOUT</a>
         </li>
 {% endblock %}
 
         </li>
     {% endfor %}
 {% endblock %}
-    
+
+{% block page.subnav %}
+    {% for item in menu%}
+        {% if item|lookup:"is_active" %}
+            {% with item|lookup:"submenu" as submenu %}
+                {% for item in submenu %}
+                    <li {% if item|lookup:"is_active" %}class="active"{% endif %}>
+                        <a href="{{ item|lookup:"url" }}">{{ item|lookup:"name" }}</a>
+                    </li>
+                {% endfor %}
+            {% endwith %}
+        {% endif %}
+    {% endfor %}
+{%endblock %}
+
 {% block page.body %}
 <div class="{% block innerpage.class %}full{% endblock %}">
 {% block body %}{% endblock %}
@@ -1,6 +1,10 @@
 --- A translation in English follows ---
 
+{% if group_creation %}
 Έχει δημιουργηθεί ο παρακάτω λογαριασμός:
+{% else %}
+Έχει ενεργοποιηθεί ο παρακάτω λογαριασμός:
+{% endif %}
 
 Email:          {{user.email}}
 First name:     {{user.first_name}}
@@ -9,10 +13,16 @@ Is active:      {{user.is_active}}
 Level:          {{user.level}}
 Invitations:    {{user.invitations}}
 
-Για την ενεργοποίησή του μπορείτε να χρησιμοποιήσετε το command line εργαλείο snf-manage sendactivation
+{% if group_creation %}
+Για την ενεργοποίησή του μπορείτε να χρησιμοποιήσετε το command line εργαλείο snf-manage user_send_activation
+{% endif %}
 --
 
+{% if group_creation %}
 The following account has been created:
+{% else %}
+The following account has been activated:
+{% endif %}
 
 Email:          {{user.email}}
 First name:     {{user.first_name}}
@@ -21,4 +31,6 @@ Is active:      {{user.is_active}}
 Level:          {{user.level}}
 Invitations:    {{user.invitations}}
 
-For its activation you can use the command line tool snf-manage sendactivation
+{% if group_creation %}
+For its activation you can use the command line tool snf-manage user_send_activation
+{% endif %}
\ No newline at end of file
diff --git a/snf-astakos-app/astakos/im/templates/im/astakosgroup_create_list.html b/snf-astakos-app/astakos/im/templates/im/astakosgroup_create_list.html
new file mode 100644 (file)
index 0000000..a198d7f
--- /dev/null
@@ -0,0 +1,59 @@
+{% extends "im/account_base.html" %}
+
+{% block body %}
+<div class="projects">
+       <div class="clearfix">
+               <p>You can create the followind type of projects:</p>
+       </div>
+       
+       <div class="two-cols clearfix dotted">
+               <div class="rt centered">
+                       <img alt="THINK ABOUT IT" src="/static/medialibrary/2012/04/proffessor.png">
+               </div>
+               <div class="lt">
+                       <h2 style="color:#B66D00;">COURSE</h2>
+               <div>
+                       <p>You wake up one morning and you need a new computer with a specific operating system and hardware requirements (or 10 new computers). You also need 10GB of storage space to store some new content you just got your hands on (or 50GB ;-)).</p>
+                       <a href="{% url group_add 'course' %}" class="submit">CREATE COURSE</a>
+               </div>
+               </div>
+       </div>
+       <div class="two-cols clearfix dotted">
+               <div class="rt centered">
+                       <img alt="THINK ABOUT IT" src="/static/medialibrary/2012/06/behind_okeanos.png">
+               </div>
+               <div class="lt">
+                       <h2 style="color:#4085A6;">PROJECT</h2>
+               <div>
+                       <p>You wake up one morning and you need a new computer with a specific operating system and hardware requirements (or 10 new computers). You also need 10GB of storage space to store some new content you just got your hands on (or 50GB ;-)).</p>
+                       <a href="{% url group_add 'project' %}" class="submit">CREATE PROJECT</a>
+               </div>
+               </div>
+       </div>
+       <div class="two-cols clearfix dotted">
+               <div class="rt centered">
+                       <img alt="THINK ABOUT IT" src="/static/medialibrary/2012/06/from_athens.png">
+               </div>
+               <div class="lt">
+                       <h2 style="color:#EF4F54;">ORGANISATION</h2>
+               <div>
+                       <p>You wake up one morning and you need a new computer with a specific operating system and hardware requirements (or 10 new computers). You also need 10GB of storage space to store some new content you just got your hands on (or 50GB ;-)).</p>
+                       <a href="{% url group_add 'organization' %}" class="submit">CREATE ORGANISATION</a>
+               </div>
+               </div>
+       </div>
+       <div class="two-cols clearfix dotted">
+               <div class="rt centered">
+                       <img alt="THINK ABOUT IT" src="/static/medialibrary/2012/04/researcher.png">
+               </div>
+               <div class="lt">
+                       <h2 style="color:#FF7CA4;">LAB</h2>
+               <div>
+                       <p>You wake up one morning and you need a new computer with a specific operating system and hardware requirements (or 10 new computers). You also need 10GB of storage space to store some new content you just got your hands on (or 50GB ;-)).</p>
+                       <a href="{% url group_add 'laboratory' %}" class="submit">CREATE LAB</a>
+               </div>
+               </div>
+       </div>
+</div>
+
+{% endblock body %}
\ 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
new file mode 100644 (file)
index 0000000..b817935
--- /dev/null
@@ -0,0 +1,196 @@
+{% 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 %}
+                                       [ ADMINISTRATOR ]
+                               {%  else %}
+                                       [ ENROLLED ]
+                               {% endif %}
+                       </em>           
+               {% endif %}
+               <span>{{ object.name|strip_http|upper }}</span>
+        </h2>
+        
+        <div class="details">
+               {% if object.is_owner %}
+                       <a href="#" class="edit">EDIT GROUP INFO</a>
+               {% endif %}
+               <div class="data">
+                       <p>{{ object.desc }}</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>
+                        </dl>
+               </div>
+               <div class="editable" style="display:none;">
+               <form action="" 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="FINISHED EDITING" />
+                    </div>
+                   {% endwith %}
+           </form>
+               </div>
+        </div>
+        <div class="full-dotted">
+                <h3>DETAILS:</h3>
+                <dl class="alt-style">
+                       <dt>Name</dt>
+                       <dd>{{ object.name|strip_http }}&nbsp;</dd>
+                       <!--<dt>Type</dt>
+                       <dd>{{object.kindname|capfirst}}&nbsp;</dd>-->
+                       <dt>Issue date:</dt>
+                       <dd>{{object.issue_date|date:"d/m/Y"}}&nbsp;</dd>
+                       <dt>Expiration Date</dt>
+                       <dd>{{object.expiration_date|date:"d/m/Y"}}&nbsp;</dd>
+                       <dt>Modaration</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 %}&nbsp;
+            </dd>
+            <dt>Max participants</dt>
+                       <dd>{% if object.max_participants%}{{object.max_participants}}{% else %}&nbsp;{% 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>
+                       {% endfor %}
+               </dl>
+               {% else %}
+            <p>No resources</p>
+        {% endif %} 
+        </div>
+     
+        <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="">Sort by</option>
+                        <option value="person__email" {% if sorting == 'person__email' %}selected{% endif %}>User Id</option>
+                        <option value="person__first_name" {% if sorting == 'person__first_name' %}selected{% endif %}>Name</option>
+                        <option value="date_joined" {% if sorting == 'date_joined' %}selected{% endif %}>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</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 %}">Reject</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>
+     
+     
+    {% if object.is_owner %}
+    <div class="full-dotted">
+        <form action="" 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>
+{% endwith %}
+{% endblock %}
diff --git a/snf-astakos-app/astakos/im/templates/im/astakosgroup_form.html b/snf-astakos-app/astakos/im/templates/im/astakosgroup_form.html
new file mode 100644 (file)
index 0000000..c9465ee
--- /dev/null
@@ -0,0 +1,101 @@
+{% 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="{{ g }}">
+                       <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">&nbsp;</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>     
+</form>
+        
+<script>
+       
+</script>       
+{% endblock %}
\ No newline at end of file
diff --git a/snf-astakos-app/astakos/im/templates/im/astakosgroup_form_summary.html b/snf-astakos-app/astakos/im/templates/im/astakosgroup_form_summary.html
new file mode 100644 (file)
index 0000000..6929734
--- /dev/null
@@ -0,0 +1,79 @@
+{% 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>3. CONFIRM YOUR REQUEST</legend>
+<!-- 
+               {% for k,v in data.iteritems %}
+                       <input type="hidden" name="{{ k }}" value="{{ v }}">
+               {% endfor %}
+ -->
+        {% include "im/form_render.html" %}
+               
+               <p>{{ data.desc|safe }}</p>
+               <dl class="alt-style">                      
+                       <dt>Homepage Url</dt>
+                       <dd>{{ data.homepage }}&nbsp;</dd>
+               </dl>
+               
+               <div class="full-dotted">
+                        <h3>DETAILS:</h3>
+                        <dl class="alt-style">                     
+                           <dt>Name</dt>
+                               <dd>{{ data.name|strip_http }}&nbsp;</dd>
+                               <!--<dt>Type</dt>
+                               <dd>Course&nbsp;</dd>-->
+                               <dt>Issue date:</dt>
+                               <dd>{{ data.issue_date|date:"d/m/Y"}}&nbsp;</dd>
+                               <dt>Expiration Date</dt>
+                               <dd>{{ data.expiration_date|date:"d/m/Y"}}&nbsp;</dd>
+                               <dt>Modaration</dt>
+                               <dd>{{ data.moderation_enabled|yesno:"Yes, No" }}</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>
+                       <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>
+                               {% 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/astakosgroup_list.html b/snf-astakos-app/astakos/im/templates/im/astakosgroup_list.html
new file mode 100644 (file)
index 0000000..dd462c8
--- /dev/null
@@ -0,0 +1,305 @@
+{% extends "im/account_base.html" %}
+
+{% load filters %}
+
+{% block page.body %}
+<div class="maincol {% block innerpage.class %}{% endblock %}">
+    <div class="projects">
+           <h2>GROUPS</h2>
+           {% if form %}
+                   <p>You can search for a group by name</p>
+                   <form action="{% url group_search %}" method="post" class="withlabels signup">{% csrf_token %}
+                           {% include "im/form_render.html" %}
+                           <div class="form-row submit">
+                               <input type="submit" class="submit altcol" value="SEARCH" />
+                           </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="Show all groups" />
+                       </div>
+                   </form>
+           {% else %}
+    
+               <p>Okeanos grants resources according to activities organized by groups.
+                  Join or create a group to get access to more resources.
+                  <a href="{% url group_search %}">Join an existing one</a>
+                  or
+                  <!--<a href="{% url group_create_list %}">Create a new group</a>-->
+                  <a href="{% url group_add 'default' %}">create a new group</a>.
+               </p>
+               
+               <div class="widjets"> 
+                       <!--<a href="#" class="widjet-x" title="remove boxes">X</a>-->
+                       <ul class="clearfix">   
+                               <li class="create">
+                                       <div>
+                                               <div class="wrap">
+                                                       <h2>WELCOME!</h2>
+                                                       <p><a href="{% url group_add 'course' %}"><img alt="THINK ABOUT IT" src="/static/im/images/create.png"></a></p>
+                                                       <p class="txt">Connect with a world of people who share your passions.<br>With millions of groups at your fingertips, it's easy to find the group that's best for you -- no matter your interest.</p>
+                                                       <p><a href="{% url group_add 'default' %}">create a group ></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">
+                                                       <h2>ALREADY AWARE?</h2>
+                                                       <p><a href="{% url group_search %}"><img alt="THINK ABOUT IT" src="/static/im/images/join.png"></a></p>
+                                                       <p class="txt">Well, this is the place to start!<br>sdofuisd ofuaofi usdiof uiofu osifuaoi ufisdfiousf oiusd<br></p>
+                                                       
+                                                       <p><a href="{% url group_search %}">join a group ></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="">Sort by Name</option>      
+                                   <option value="issue_date" {% if sorting == 'issue_date' %}selected{% endif %}>Issue date</option>                  
+                                   <option value="expiration_date" {% if sorting == 'expiration_date' %}selected{% endif %}>Expiration Date</option>
+                                   <option value="approved_members_num" {% if sorting == 'approved_members_num' %}selected{% endif %}>Participants</option> 
+                                       </select>
+                                       <input type="hidden" name="q" value="{{q}}"/>
+                               </div>
+                       </form>
+            <table class="alt-style complex" id="searchResults">
+                <caption>
+                    SEARCH RESULTS
+                </caption>
+                <thead>
+                  <tr>
+                    <th>Name</th>
+                    <!--<th>Type</th>-->
+                    <th>Issued</th>
+                    <th>Expires</th>
+                     
+                    <th>Enrolled</th>
+                   
+                     
+                    <th>Status</th>
+                    <th>&nbsp;</th>
+                  
+                  </tr>
+                </thead>
+                <tbody>
+                  {% for o in object_list %}
+                   <tr class="{% cycle 'tr1' 'tr2' %}">
+                           <td><a href="{% url group_detail o.id %}" title="visit group page">{{o.groupname|rcut:"/"}}</a></td>
+                           <!--td>{{o.kindname|capfirst}}</td-->
+                           <td>{{o.issue_date|date:"d/m/Y"}}</td>
+                           <td>{{o.expiration_date|date:"d/m/Y"}}</td>
+                           <td>{{o.approved_members_num}}</td>
+                           
+                           <td>
+                               <div class="msg-wrap">
+                                        
+                                   {% if o.is_member %}
+                                       {% if o.membership_approval_date %}
+                   
+                                       
+                                               {% if not o.is_owner %}
+                                                   Registered
+                                                   <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|rcut:"/" }}</a><br>
+                                                                       {% if o.desc %}Description:{{o.desc|truncatewords:30}}{% endif %}<br><br>
+                                                                       
+                                                                       <a href="#" class="yes submit">Yes</a>&nbsp;&nbsp;&nbsp;<a href="#" class="no submit">No</a>
+                                                               </div>
+                                               {% else %}
+                                                       Owner
+                                               {% endif %}
+                                       
+                                           
+               
+                                       {% else %}
+                                           Pending
+                                       {% endif %}
+                                   {% else %}
+                                           Not member 
+                                               <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|rcut:"/" }}</a><br>
+                                                                       {% if o.desc %}Description:{{o.desc|truncatewords:30}}{% endif %}<br><br>
+                                                                       
+                                                                       <a href="#" class="yes submit">Yes</a>&nbsp;&nbsp;&nbsp;<a href="#" class="no submit">No</a>
+                                                               </div>
+                                           
+                                   {% endif %}
+                               </div>
+                           </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 %}
+                
+                   <div >
+                                       <form method="GET" class="minimal" action="#allGroups" id="mygroups">
+                                               <div class="form-row">
+                                                   <select name="sorting"  class="dropkicked"  tabindex="1">
+                                                           <option value="">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 GROUPS</caption>
+                                   <thead>
+                                     <tr>
+                                       <th>Name</th>
+                                       <!--th>Type</th-->
+                                       <th>Issued</th>
+                                       <th>Expires</th>
+                                       <th>Enrolled</th>
+                                       <th>Status</th>
+                                       <th class="centered">Moderated</th>
+                                       <th>&nbsp;</th>
+                                       
+                                     </tr>
+                                   </thead>
+                                   <tbody>
+                                     {% for o in page_obj.object_list %}
+                                     <tr class="{% cycle 'tr1' 'tr2' %}">
+                                       <td><a  href="{% url group_detail o.id %}" title="visit group page">{{o.groupname|rcut:"/" }}</a></td>
+                                       <!--td>{{o.kindname|capfirst}}</td-->
+                                       <td>{{o.issue_date|date:"d/m/Y"}}</td>
+                                       <td>{{o.expiration_date|date:"d/m/Y"}}</td>
+                                       <td>{{ o.approved_members_num }}</td>
+                                       <td>
+                                               <div class="msg-wrap">
+                                               {% if user.email = o.groupowner %}
+                                                       {% if o.is_enabled %}
+                                                               Owner
+                                                       {% else %}
+                                                               Pending
+                                                       {% endif %}
+                                               {% else %}
+                                                       {% if o.is_enabled %}
+                                                               {% if o.membership_status %}
+                                                                   Registered
+                                                               <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|rcut:"/" }}</a><br>
+                                                                               {% if o.desc %}Description:{{o.desc|truncatewords:30}}{% endif %}<br><br>
+                                                                               
+                                                                               <a href="#" class="yes submit">Yes</a>&nbsp;&nbsp;&nbsp;<a href="#" class="no submit">No</a>
+                                                                       </div>
+                                                               {% else %}
+                                                                   Pending
+                                                               {% endif %}
+                                                       {% else %}
+                                                               -
+                                                       {% endif %}
+                                               {% endif %}
+                                               
+                                               </div>
+                                       </td>
+                                       <td class="centered">{% 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>
+                
+               {% endwith %} 
+               {% endwith %} 
+               {% endif %}
+               {% if form %}
+                   {% if q %}
+                       <h2>No groups found!</h2>
+                   {% endif %}
+               {% endif %}
+      {% endif %}
+    {% endwith %}
+</div>
+</div>  
+{% endblock %}
diff --git a/snf-astakos-app/astakos/im/templates/im/astakosuserquota_list.html b/snf-astakos-app/astakos/im/templates/im/astakosuserquota_list.html
new file mode 100644 (file)
index 0000000..1a24dc0
--- /dev/null
@@ -0,0 +1,98 @@
+{% extends "im/account_base.html" %}
+
+{% load filters %}
+
+{% block page.body %}
+<div class="maincol {% block innerpage.class %}{% endblock %}">
+    <div class="stats clearfix">
+               <ul>
+                       {% for r in data.resources %}
+                        
+               <li class="clearfix  {{ r.load_class }} {{ r.name}}">
+                       <div class="img-wrap">&nbsp;</div>
+                       <div class="info">
+                               <h3>{{ r.description }} </h3>
+                               <p>
+                                       {{ r.ratio|floatformat }}% Used<br>
+                                       You are using {{ r.currValue }} {{ r.unit }} out of your {{ r.maxValue }}{{ r.unit }} 
+                                       {% if r.maxValue == '1' %}
+                                               {{ r.name|capfirst}}
+                                       {% else %} 
+                                               {{ r.plural|capfirst }}
+                                       {% endif %}
+                                        - Aouch!
+                               </p>
+                       </div>
+                       <div class="bar">
+                               <div><span style="width:{{ r.ratio|floatformat }}%;">{{ r.ratio|floatformat }}% &nbsp;&nbsp;</span></div>
+                       </div>
+               </li>
+               {% endfor %}
+       </ul>
+    </div>    
+    <!--
+    <div class="section">
+            {% for k, v in user.quota|items %}
+                <strong>{{k}}</strong>
+                <table class="zebra-striped id-sorted">
+                    <thead>
+                      <tr>
+                        <th>Limit (Group)</th>
+                      </tr>
+                    </thead>
+                    <tbody>
+                    {% for m in user.membership_set.select_related.all %}
+                        {% if m.group.is_enabled %}
+                            {% with m.group.quota as quota %}
+                            {% if  quota %}
+                                {% for kk, vv in quota|items %}
+                                    {% if k == kk %}
+                                    <tr>
+                                        <td>{{ vv }} ({{m.group.name}})</td>
+                                    </tr>
+                                    {% endif %}
+                                {% endfor %}
+                            {% endif %}
+                            {% endwith %}
+                        {% endif %}
+                    {% endfor %}
+                                    <tr>
+                                        <td><strong>{{ v }}</strong></td>
+                                    </tr>
+                                    <tr/>
+                    </tbody>
+                </table>
+            {% endfor %}
+    </div>
+        <form action="" method="post"
+                   class="withlabels">{% csrf_token %}
+                   {% include "im/form_render.html" %}
+           </form>
+           <table>
+           {% for q in object_list %}
+               {% if q.group.is_enabled %}
+                   <tr>
+                       <td>{{ q.uplimit }}</td><td>({{ q.group }})</td>
+                   </tr>
+               {% endif %}
+           {% endfor %}
+           {% if form.cleaned_data %}
+            {% with form.cleaned_data|lookup:'resource' as resource %}
+                {% if resource %}
+                    {% for q in user.astakosuserquota_set.all %}
+                        {% if q.resource == resource %}
+                        <tr>
+                            <td>{{ q.uplimit }}</td><td>(assigned to me)</td>
+                        </tr>
+                        {% endif %}
+                    {% endfor %}
+                        <tr>
+                            <td>{{ user.quota|lookup:resource }}</td><td>(total)</td>
+                        </tr>
+                {% endif %}
+            {% endwith %}
+        {% endif %}
+           </table>-->
+    </div>
+</div>
+{% endblock %}
index 31de473..8ccc746 100644 (file)
@@ -27,7 +27,7 @@
 
   {% block page.css %}
        
-      <link href='https://fonts.googleapis.com/css?family=Didact+Gothic&subset=latin' rel='stylesheet' type='text/css'>
+      <link href='https://fonts.googleapis.com/css?family=Open+Sans:400,600,700&subset=latin,greek-ext,greek' rel='stylesheet' type='text/css'>
       <link rel="stylesheet" type="text/css" href="{{ IM_STATIC_URL }}css/global.css">
       <link rel="stylesheet" type="text/css" href="{{ IM_STATIC_URL }}css/print.css" media="print">
       <!--[if lte IE 7]>
@@ -35,7 +35,7 @@
       <![endif]-->
       <link rel="stylesheet" media="screen and (max-width: 960px)" href="{{ IM_STATIC_URL }}css/max960.css"/>
       <link rel="stylesheet" media="screen and (max-width: 768px)" href="{{ IM_STATIC_URL }}css/max768.css"/>  
-      <link rel="stylesheet" media="screen and (max-width: 480px)" href="{{ IM_STATIC_URL }}css/max480.css"/>  
+      <link rel="stylesheet" media="screen and (max-width: 480px)" href="{{ IM_STATIC_URL }}css/max480.css"/> 
       <link rel="stylesheet" type="text/css" href="{{ IM_STATIC_URL }}css/jquery-ui-1.8.21.custom.css"/>
       
   {% endblock page.css %}
       <script src="{{ IM_STATIC_URL }}js/modernizr-2.0.6.js"></script> 
       <script src="{{ IM_STATIC_URL }}js/jquery.js"></script>  
       <script src="{{ IM_STATIC_URL }}js/jquery.infieldlabel.js"></script>     
-      <script src="{{ IM_STATIC_URL }}js/forms.js"></script>    
+      <script src="{{ IM_STATIC_URL }}js/forms.js"></script>
+      <script src="{{ IM_STATIC_URL }}js/jquery.dropkick-1.0.0.js"></script>
+      <script src="{{ IM_STATIC_URL }}js/jquery-ui-1.8.21.custom.min.js"></script>
+      <script src="{{ IM_STATIC_URL }}js/jquery.tablesorter.js"></script>               
       <script src="{{ IM_STATIC_URL }}js/common.js"></script> 
   {% endblock headjs %}
   {% block endhead %}{% endblock endhead %}
diff --git a/snf-astakos-app/astakos/im/templates/im/billing.html b/snf-astakos-app/astakos/im/templates/im/billing.html
new file mode 100644 (file)
index 0000000..f3ad1f3
--- /dev/null
@@ -0,0 +1,150 @@
+{% extends "im/account_base.html" %}
+
+{% load filters %}
+
+
+
+{% block page.body %}
+<div class="billing list"> 
+       {{ data }}
+       <div class="highlight">
+               
+               <em>{{ data.remainingCredits|floatformat:2 }}</em> credits remaining
+               <span class="info foo"> 
+                   <em>more info</em>
+                   <span>|<br>This month you are given
+                        {% with data|lookup:'bill_addcredits' as bill_addcredits %}
+                                                {{ bill_addcredits.0.totalCredits|floatformat:0}} 
+                                       {% endwith %} new credits.
+                       </span>
+           </span>
+       </div>
+       
+       <h2><span>Billing statement for </span><em>[ {{ today.month|month_name|upper }} {{ today.year }} ]</em></h2>
+       {% if data.bill|length %}
+               <p>
+                       <em>
+                               This page shows billing report for the period 1 {{ today.month|month_name }} {{ today.year }}- {{ month_last_day }} {{ today.month|month_name }} {{ today.year }}
+                       </em>
+               </p>
+       
+        
+       
+               <br><br>
+               <table class="alt-style">
+                   <tr>
+                       <th>Service</th>
+                       <th>Monthly Usage</th>
+                       <th class="last">Charged Credits</th>
+                   </tr>
+                   <tr>
+                       <td>Cyclades 
+                               {% with data|lookup:'bill_vmtime' as bill_vmtime %}
+                                       
+                                                {% if bill_vmtime.0.totalCredits != '0.0'  %}
+                                                       <a href="" class="more-info">&nbsp;</a>
+                                                {% endif%}
+                                       {% endwith %}
+                       </td>
+                       <td>
+                                       {% with data|lookup:'bill_vmtime' as bill_vmtime %}
+                                                {{ bill_vmtime.0.totalUnits|floatformat:0}} Hr
+                                       {% endwith %}
+                               </td>
+                       <td  class="last">
+                                       {% with data|lookup:'bill_vmtime' as bill_vmtime %}
+                                                {{ bill_vmtime.0.totalCredits|floatformat:2}}
+                                       {% endwith %}
+                               
+                               </td>
+                   </tr>
+                   <tr class="innertable" style="display:none">
+                       <td colspan="3">
+                               <div class="table-div">
+                                       <table class="alt-style">
+                                                   
+                                                   
+                                                   <tr>
+                                                       <th>VM</th>
+                                                       <th>Flavor</th>
+                                                       <th>Vmtime</th>
+                                                       <th class="last">Charged Credits</th>
+                                                   </tr>
+                                                   {% with data|lookup:'bill_vmtime' as bill_vmtime %}
+                                                               {% for d in  bill_vmtime %}
+                                                                       {% for vm in  d.details %}
+                                                                               <tr>
+                                                                                       <td>{{ vm.resourceName }}</td>
+                                                                                       <td>flavor?</td>
+                                                                                       <td>{{ vm.totalElapsedTime|todate|timeuntil:zerodate }}</td>
+                                                                                       <td class="last">{{ vm.totalUnits }}</td>
+                                                                               </tr>
+                                                                               
+                                                                       {% endfor %}
+                                                               {% endfor %}
+                                                       {% endwith %} 
+                                                  
+                                                    
+                                                </table>
+                                        </div>
+                       </td>
+                   </tr>
+                   <tr>
+                       <td>Pithos +</td>
+                       <td>
+                                       {% with data|lookup:'bill_diskspace' as bill_diskspace %}
+                                               {{ bill_diskspace.0.totalUnits|floatformat:0}} {{ bill_diskspace.0.unitName }}
+                                       {% endwith %}
+                                       
+                               </td>
+                       <td class="last">
+                                       {% with data|lookup:'bill_diskspace' as bill_diskspace %}
+                                               {{ bill_diskspace.0.totalCredits|floatformat:2}}
+                                       {% endwith %}
+                               
+                               </td>
+                   </tr>
+                   <tr>
+                       <td>Total Credits</td>
+                       <td>&nbsp;</td>
+                        
+                       <td class="sum last">{{ data.deductedCredits|floatformat:2 }}</td>
+                   </tr>
+               </table>
+       {% else %}
+               <p>
+                       <em>
+                               There is no billing statement for this month.
+                       </em>
+               </p>
+       {% endif %}
+       
+       
+        <form action={% url astakos.im.views.billing %} class="withlabels"  method="POST">{% csrf_token %}
+               <div class="form-row">
+                       <label for="month">Choose another month</label>
+                       <select name="datefrom">
+                               {% with user.date_joined|monthssince as periods %}
+                                       {% for period in periods %}
+                                               <option value="{{ period.2 }}" {% if period.2 == start  %}selected="selected"{% endif%}> 
+                                                       
+                                                          {{ period.1|month_name }} {{period.0}} 
+                                               </option>
+                                       {% endfor %}
+                               {% endwith %}
+                       </select>
+               </div>
+               <div class="form-row submit">
+               <input type="submit" value="VIEW">
+           </div> 
+       </form>
+       
+       <br><br><br>
+       <p>You can download a detailed activity report in Comma Separated Value (CSV) format or as txt file for AUGUST 2012.<br />
+               <a href="#">Download CSV</a>, <a href="#">Download .txt</a>
+       </p>
+                
+</div>
+
+{% endblock %}
index 6a23e36..1c2a622 100644 (file)
@@ -1,7 +1,7 @@
 {% extends "im/account_base.html" %}
 
 {% block body %}
-<form action="{% url astakos.im.views.feedback %}" method="post"
+<form action="{% url feedback %}" method="post"
     class="withlabels">{% csrf_token %}
     
     {% with feedback_form as form %}
index 0169929..b75c032 100644 (file)
@@ -1,6 +1,6 @@
 {% block footer_content %}
  
-<p class="termslink" style="float:right"><a href="{% url latest_terms %}">Terms of use</a></p>
+<p class="termslink" style="float:right"><a href="{% url latest_terms %}">Terms of service</a></p>
 <p>Copyright 2011-2012 <a href="http://www.grnet.gr" target="_blank" title="GRNET S.A.">GRNET S.A.</a> All rights reserved.</p>
  
 {% endblock %}
diff --git a/snf-astakos-app/astakos/im/templates/im/group_creation_notification.txt b/snf-astakos-app/astakos/im/templates/im/group_creation_notification.txt
new file mode 100644 (file)
index 0000000..f3c4e0b
--- /dev/null
@@ -0,0 +1,38 @@
+--- A translation in English follows ---
+
+Έχει δημιουργηθεί το παρακάτω group:
+
+Id:                             {{group.id}}
+Name:                           {{group.name}}
+Type:                           {{group.kind}}
+Issue date:                     {{group.issue_date|date:"d/m/Y"}}
+Expiration date:                {{group.expiration_date|date:"d/m/Y"}}
+Moderation:                     {{group.moderation_enabled}}
+Owner:                          {{owner}}
+Maximum participant number:    {{group.max_participants}}
+Policies:
+{% for p in policies %}
+    {{p.service}}.{{p.resource}}: uplimit:{% if p.uplimit %}{{p.uplimit}}{% else %}inf{% endif %}
+{% endfor %}
+
+Για την ενεργοποίησή του μπορείτε να χρησιμοποιήσετε το command line εργαλείο:
+snf-manage group_update <group_id> --enable
+--
+
+The following account has been created:
+
+Id:                             {{group.id}}
+Name:                           {{group.name}}
+Type:                           {{group.kind}}
+Issue date:                     {{group.issue_date|date:"d/m/Y"}}
+Expiration date:                {{group.expiration_date|date:"d/m/Y"}}
+Moderation:                     {{group.moderation_enabled}}
+Owner:                          {{owner}}
+Maximum participant number:    {{group.max_participants}}
+Policies:
+{% for p in policies %}
+    {{p.service}}.{{p.resource}}: uplimit:{% if p.uplimit %}{{p.uplimit}}{% else %}inf{% endif %}
+{% endfor %}
+
+For its activation you can use the command line tool:
+snf-manage group_update <group_id> --enable
index 4840334..e88b90d 100644 (file)
@@ -15,7 +15,7 @@
            {% endwith %}
        
            <div class="form-row submit">
-               <input type="submit" class="submit altcol" value="SEND" />
+               <input type="submit" class="submit altcol" value="INVITE" />
            </div>
          </form>
          {% endif %}
@@ -26,7 +26,7 @@
            
            <h2>You have <em>{{ inviter.invitations }}</em> invitation{{ inviter.invitations|pluralize }} left.</h2>
            {% if sent|length %}
-             <table class="zebra-striped id-sorted">
+             <table class="alt-style">
                <thead>
                  <tr>
                    <th>Email</th>
@@ -51,5 +51,4 @@
 
 
 
-{% endblock %}
-
+{% endblock %}
\ No newline at end of file
index 277a7e3..416f9c7 100644 (file)
@@ -2,7 +2,7 @@
 
 {% block body %}
 
-<form action={%url astakos.im.views.edit_profile %} method="post" class="withlabels">{% csrf_token %}
+<form action={%url edit_profile %} method="post" class="withlabels">{% csrf_token %}
     
     {% with profile_form as form %}
     {% include "im/form_render.html" %}
     </div>
 
 </form>
+
+<div class="two-cols-links">
+       <p><a href="{% url password_change %}">Change Password</a></p>
+       <p>
+               <a href="https://okeanos.grnet.gr/home/">Back to ~okeanos</a>
+               <a href="https://cyclades.okeanos.grnet.gr/ui/">Take me to cyclades</a>
+               <a href="https://pithos.okeanos.grnet.gr/ui/">Take me to pithos+</a>
+       </p>
+</div>
 {% endblock body %}
diff --git a/snf-astakos-app/astakos/im/templates/im/projects/list_types.html b/snf-astakos-app/astakos/im/templates/im/projects/list_types.html
new file mode 100644 (file)
index 0000000..6606974
--- /dev/null
@@ -0,0 +1,65 @@
+{% extends "im/account_base.html" %}
+{% block page.subnav %}
+
+       <li><a href="../">Overview</a></li>
+       <li class="active"><a href="#">Create</a></li>
+       <li><a href="../join/">Join</a></li>
+
+{%endblock %}
+{% block body %}
+<div class="projects">
+       <div class="clearfix">
+               <p>You can create the followind type of projects:</p>
+       </div>
+       
+       <div class="two-cols clearfix dotted">
+               <div class="rt centered">
+                       <img alt="THINK ABOUT IT" src="/static/medialibrary/2012/04/proffessor.png">
+               </div>
+               <div class="lt">
+                       <h2 style="color:#B66D00;">COURSE</h2>
+               <div>
+                       <p>You wake up one morning and you need a new computer with a specific operating system and hardware requirements (or 10 new computers). You also need 10GB of storage space to store some new content you just got your hands on (or 50GB ;-)).</p>
+                       <a href="course/" class="submit">CREATE COURSE</a>
+               </div>
+               </div>
+       </div>
+       <div class="two-cols clearfix dotted">
+               <div class="rt centered">
+                       <img alt="THINK ABOUT IT" src="/static/medialibrary/2012/06/behind_okeanos.png">
+               </div>
+               <div class="lt">
+                       <h2 style="color:#4085A6;">PROJECT</h2>
+               <div>
+                       <p>You wake up one morning and you need a new computer with a specific operating system and hardware requirements (or 10 new computers). You also need 10GB of storage space to store some new content you just got your hands on (or 50GB ;-)).</p>
+                       <a href="#" class="submit">CREATE PROJECT</a>
+               </div>
+               </div>
+       </div>
+       <div class="two-cols clearfix dotted">
+               <div class="rt centered">
+                       <img alt="THINK ABOUT IT" src="/static/medialibrary/2012/06/from_athens.png">
+               </div>
+               <div class="lt">
+                       <h2 style="color:#EF4F54;">ORGANISATION</h2>
+               <div>
+                       <p>You wake up one morning and you need a new computer with a specific operating system and hardware requirements (or 10 new computers). You also need 10GB of storage space to store some new content you just got your hands on (or 50GB ;-)).</p>
+                       <a href="#" class="submit">CREATE ORGANISATION</a>
+               </div>
+               </div>
+       </div>
+       <div class="two-cols clearfix dotted">
+               <div class="rt centered">
+                       <img alt="THINK ABOUT IT" src="/static/medialibrary/2012/04/researcher.png">
+               </div>
+               <div class="lt">
+                       <h2 style="color:#FF7CA4;">LAB</h2>
+               <div>
+                       <p>You wake up one morning and you need a new computer with a specific operating system and hardware requirements (or 10 new computers). You also need 10GB of storage space to store some new content you just got your hands on (or 50GB ;-)).</p>
+                       <a href="{% url group_add 'laboratory' %}" class="submit">CREATE LAB</a>
+               </div>
+               </div>
+       </div>
+</div>
+
+{% endblock body %}
index d3f57b9..d33383d 100644 (file)
@@ -5,7 +5,7 @@
 {% endblock title%}
 
 {% block body %}
-<form action={%url astakos.im.views.register%} method="post">{% csrf_token %}
+<form action={%url register%} method="post">{% csrf_token %}
     {{ form.as_p }}
 <div>
     <button type="submit" class="btn primary">Register</button>
diff --git a/snf-astakos-app/astakos/im/templates/im/resource_list.html b/snf-astakos-app/astakos/im/templates/im/resource_list.html
new file mode 100644 (file)
index 0000000..fe1def0
--- /dev/null
@@ -0,0 +1,34 @@
+{% extends "im/account_base.html" %}
+
+{% load filters %}
+
+{% block page.body %}
+<div class="maincol {% block innerpage.class %}{% endblock %}">
+       <div class="stats clearfix">
+               <ul>
+                       {% for rname, rdata in resource_catalog.resources.items %}
+                               <li class="clearfix  {{ rdata.load_class }} {{ rdata.verbose_name}}">
+                                       <div class="img-wrap">&nbsp;</div>
+                                       <div class="info">
+                                               <h3>{{ rdata.report_desc }}</h3>
+                                               <p>
+                                               {{ rdata.ratio|floatformat }}% Used<br>
+                                               You are using 
+                                               {% if rdata.unit == 'bytes' %}
+                                                       {{ rdata.currValue|sizeof_fmt  }} out of your {{ rdata.maxValue|sizeof_fmt }}
+                                               {% else %}
+                                                       {{ rdata.currValue }} {{ rdata.unit }} out of your {{ rdata.maxValue }} {{ rdata.unit }}
+                                               {% endif %}             
+                                               {% if rdata.is_abbreviation %}{{ rdata.verbose_name|upper }}{% else %}{{ rdata.verbose_name }}{% endif %}{% if rdata.maxValue|floatformat:"0" != "1" and not rdata.unit %}s {% endif  %}
+                                               </p>
+                                       </div>
+                                       <div class="bar">
+                                               <div><span style="width:{{ rdata.ratio|floatformat }}%;">{{ rdata.ratio|floatformat }}% &nbsp;&nbsp;</span></div>
+                                       </div>
+                               </li>
+                       {% endfor%}
+               </ul>
+                        
+       </div>    
+</div>
+{% endblock %}
index eaeb5be..6faf1ed 100644 (file)
@@ -12,7 +12,7 @@
 {% block body.right %}
     {% if "local" in im_modules %}
       <div class="form-stacked">
-        <form action="{% url astakos.im.views.signup %}" method="post"
+        <form action="{% url signup %}" method="post"
             class="innerlabels signup">{% csrf_token %}
           <h2><span>SIGN UP</span></h2>
             <input type="hidden" name="next" value="{{ next }}">
index 4ac7c12..bf98755 100644 (file)
@@ -10,7 +10,7 @@
     </div>
 
 <div class="section">
-    <form action="{% url astakos.im.views.signup %}" method="post" class="login innerlabels">{% csrf_token %}
+    <form action="{% url signup %}" method="post" class="login innerlabels">{% csrf_token %}
                 {% with thirdparty_signup_form as form %}
                 {% include "im/form_render.html" %}
                 {% endwith %}
index 4e2c85d..80d6348 100644 (file)
@@ -15,7 +15,7 @@
 {% block body.right %}
     {% if "local" in im_modules %}
       <div class="form-stacked">
-        <form action="{% url astakos.im.views.signup %}" method="post"
+        <form action="{% url signup %}" method="post"
             class="innerlabels signup">{% csrf_token %}
           <h2><span>SIGN UP</span></h2>
             <input type="hidden" name="next" value="{{ next }}">
diff --git a/snf-astakos-app/astakos/im/templates/im/timeline.html b/snf-astakos-app/astakos/im/templates/im/timeline.html
new file mode 100644 (file)
index 0000000..d2d1c30
--- /dev/null
@@ -0,0 +1,31 @@
+{% extends "im/account_base.html" %}
+
+{% block page.body %}
+<div class="projects">
+       <div class="maincol {% block innerpage.class %}{% endblock %}">
+           <form action="" method="post"
+                   class="withlabels">{% csrf_token %}
+                   {% include "im/form_render.html" %}
+                   <div class="form-row submit">
+                       <input type="submit" class="submit altcol" value="SUBMIT" />
+                   </div>
+           </form>
+       </div>
+</div>
+<div class="timeline">
+<table>
+    <tr style="color:black; font-weight:bold;">
+    {% for i in timeline_header %}
+        <td>{{i}}</td>
+    {% endfor %}
+    </tr>
+{% for o in timeline_body %}
+    <tr style="color:black">
+    {% for i in o %}
+        <td>{{i}}</td>
+    {% endfor %}
+    </tr>
+{% endfor %}
+</table>
+</div>
+{% endblock %}
index a18e05b..eace8a1 100644 (file)
@@ -1,7 +1,7 @@
 {% extends "im/account_base.html" %}
 
 {% block body %}
-<form action="{% url astakos.im.views.change_email %}" method="post"
+<form action="{% url email_change %}" method="post"
     class="withlabels">{% csrf_token %}
 
     {% include "im/form_render.html" %}
index af0f864..520896e 100644 (file)
@@ -3,6 +3,6 @@
 {%block page.title %}Logout{% endblock %}
 {% block body %}
 <div class="section">
-    <h2>You have successfully logged out. <a href="{% url astakos.im.views.index %}">Login</a>.</h2>
+    <h2>You have successfully logged out. <a href="{% url index %}">Login</a>.</h2>
 </div>
 {% endblock %}
index b8b18b3..2958fa6 100644 (file)
@@ -1,5 +1,5 @@
 from django import template
-from django.core.urlresolvers import reverse, resolve
+from django.core.urlresolvers import resolve
 from django.conf import settings
 
 register = template.Library()
@@ -15,10 +15,12 @@ MESSAGES_VIEWS_MAP = getattr(settings, 'ASTAKOS_MESSAGES_VIEWS_MAP', {
     'astakos.im.views.feedback': 'PROFILE_MESSAGES',
 })
 
+
 @register.tag(name='display_messages')
 def display_messages(parser, token):
     return MessagesNode()
 
+
 class DummyMessage(object):
     def __init__(self, type, msg):
         self.message = msg
@@ -27,6 +29,7 @@ class DummyMessage(object):
     def __repr__(self):
         return "%s: %s" % (self.tags, self.message)
 
+
 class MessagesNode(template.Node):
 
     def get_view_messages(self, context):
@@ -53,7 +56,8 @@ class MessagesNode(template.Node):
             cls = messages[-1].tags
             content = '<div class="top-msg active %s">' % cls
             for msg in messages:
-                content += '<div class="msg %s">%s</div>' % (msg.tags, msg.message)
+                content += '<div class="msg %s">%s</div>' % (
+                    msg.tags, msg.message)
 
             content += '<a href="#" title="close" class="close">X</a>'
             content += '</div>'
index 9e5d10d..4e9f91d 100644 (file)
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
+import calendar
+import datetime
+import math
+
+from collections import defaultdict
+
 from django import template
+from django.core.paginator import Paginator, EmptyPage
+from django.db.models.query import QuerySet
+
+
+from astakos.im.settings import PAGINATE_BY
+
 
 register = template.Library()
 
+DELIM = ','
+
+
+@register.filter
+def monthssince(joined_date):
+    now = datetime.datetime.now()
+    date = datetime.datetime(
+        year=joined_date.year, month=joined_date.month, day=1)
+    months = []
+
+    month = date.month
+    year = date.year
+    timestamp = calendar.timegm(date.utctimetuple())
+
+    while date < now:
+        months.append((year, month, timestamp))
+
+        if date.month < 12:
+            month = date.month + 1
+            year = date.year
+        else:
+            month = 1
+            year = date.year + 1
+
+        date = datetime.datetime(year=year, month=month, day=1)
+        timestamp = calendar.timegm(date.utctimetuple())
+
+    return months
+
+
 @register.filter
 def lookup(d, key):
-    return d[key]
\ No newline at end of file
+    print d, key
+    return d.get(key)
+
+@register.filter
+def lookup_uni(d, key):
+    return d.get(unicode(key))
+
+
+@register.filter
+def dkeys(d):
+    return d.keys()
+
+
+@register.filter
+def month_name(month_number):
+    return calendar.month_name[month_number]
+
+
+@register.filter
+def todate(value, arg=''):
+    secs = int(value) / 1000
+    return datetime.datetime.fromtimestamp(secs)
+
+
+@register.filter
+def rcut(value, chars='/'):
+    return value.rstrip(chars)
+
+
+@register.filter
+def paginate(l, args):
+    l = l or []
+    page, delim, sorting = args.partition(DELIM)
+    if sorting:
+        if isinstance(l, QuerySet):
+            l = l.order_by(sorting)
+        elif isinstance(l, list):
+            default = ''
+            if sorting.endswith('_date'):
+                default = datetime.datetime.utcfromtimestamp(0)
+            l.sort(key=lambda i: getattr(i, sorting)
+                   if getattr(i, sorting) else default)
+    paginator = Paginator(l, PAGINATE_BY)
+    try:
+        paginator.len
+    except AttributeError:
+        paginator._count = len(list(l))
+    
+    try:
+        page_number = int(page)
+    except ValueError:
+        if page == 'last':
+            page_number = paginator.num_pages
+        else:
+            page_number = 1
+    try:
+        page = paginator.page(page_number)
+    except EmptyPage:
+        page = paginator.page(1)
+    return page
+
+
+@register.filter
+def concat(str1, str2):
+    if not str2:
+        return str(str1)
+    return '%s%s%s' % (str1, DELIM, str2)
+
+
+@register.filter
+def items(d):
+    if isinstance(d, defaultdict):
+        return d.iteritems()
+    return d
+
+
+@register.filter
+def get_value_after_dot(value):
+    return value.split(".")[1]
+
+@register.filter
+def strip_http(value):
+    return value.replace('http://','')[:-1]
+
+
+from math import log
+unit_list = zip(['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'], [0, 0, 0, 0, 0, 0])
+
+@register.filter
+def sizeof_fmt(num):
+    
+    """Human friendly file size"""
+    if math.isinf(num):
+        return 'Unlimited'
+    if num > 1:
+        exponent = min(int(log(num, 1024)), len(unit_list) - 1)
+        quotient = float(num) / 1024**exponent
+        unit, num_decimals = unit_list[exponent]
+        format_string = '{0:.%sf} {1}' % (num_decimals)
+        return format_string.format(quotient, unit)
+    if num == 0:
+        return '0 bytes'
+    if num == 1:
+        return '1 byte'
+    else:
+       return '';
+   
+@register.filter
+def isinf(v):
+    if math.isinf(v):
+        return 'Unlimited'
+    else:
+        return v
\ No newline at end of file
index 8bc5313..0f2f0e6 100644 (file)
@@ -31,8 +31,7 @@
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
-from django.conf.urls.defaults import patterns, include, url
-from django.contrib.auth.views import password_change
+from django.conf.urls.defaults import patterns, url
 
 from astakos.im.forms import (ExtendedPasswordResetForm,
                               ExtendedPasswordChangeForm,
@@ -42,16 +41,31 @@ from astakos.im.settings import IM_MODULES, INVITATIONS_ENABLED, EMAILCHANGE_ENA
 urlpatterns = patterns('astakos.im.views',
     url(r'^$', 'index', {}, name='index'),
     url(r'^login/?$', 'index', {}, name='login'),
-    url(r'^profile/?$', 'edit_profile'),
-    url(r'^feedback/?$', 'feedback'),
-    url(r'^signup/?$', 'signup', {'on_success':'im/login.html', 'extra_context':{'login_form':LoginForm()}}),
-    url(r'^logout/?$', 'logout', {'template':'im/login.html', 'extra_context':{'login_form':LoginForm()}}),
-    url(r'^activate/?$', 'activate'),
+    url(r'^profile/?$','edit_profile', {}, name='edit_profile'),
+    url(r'^feedback/?$', 'feedback', {}, name='feedback'),
+    url(r'^signup/?$', 'signup', {'on_success': 'im/login.html', 'extra_context': {'login_form': LoginForm()}}, name='signup'),
+    url(r'^logout/?$', 'logout', {'template': 'im/login.html', 'extra_context': {'login_form': LoginForm()}}, name='logout'),
+    url(r'^activate/?$', 'activate', {}, name='activate'),
     url(r'^approval_terms/?$', 'approval_terms', {}, name='latest_terms'),
     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'^send/activation/(?P<user_id>\d+)/?$', 'send_activation', {}, name='send_activation'),
+    url(r'^resources/?$', 'resource_list', {}, name='resource_list'),
+    url(r'^billing/?$', 'billing', {}, name='billing'),
+    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'),
+    url(r'^group/(?P<group_id>\d+)/?$', 'group_detail', {}, name='group_detail'),
+    url(r'^group/search/?$', 'group_search', {}, name='group_search'),
+    url(r'^group/all/?$', 'group_all', {}, name='group_all'),
+    url(r'^group/(?P<group_id>\d+)/join/?$', 'group_join', {}, name='group_join'),
+    url(r'^group/(?P<group_id>\d+)/leave/?$', 'group_leave', {}, name='group_leave'),
+    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')
 )
 
+
 if EMAILCHANGE_ENABLED:
     urlpatterns += patterns('astakos.im.views',
         url(r'^email_change/?$', 'change_email', {}, name='email_change'),
@@ -60,8 +74,8 @@ if EMAILCHANGE_ENABLED:
 )
     
 urlpatterns += patterns('astakos.im.target',
-    url(r'^login/redirect/?$', 'redirect.login')
-)
+                        url(r'^login/redirect/?$', 'redirect.login')
+                        )
 
 if 'local' in IM_MODULES:
     urlpatterns += patterns('astakos.im.target',
@@ -84,8 +98,8 @@ if 'local' in IM_MODULES:
 
 if INVITATIONS_ENABLED:
     urlpatterns += patterns('astakos.im.views',
-        url(r'^invite/?$', 'invite')
-    )
+                            url(r'^invite/?$', 'invite', {}, name='invite')
+                            )
 
 if 'shibboleth' in IM_MODULES:
     urlpatterns += patterns('astakos.im.target',
@@ -95,22 +109,29 @@ if 'shibboleth' in IM_MODULES:
 
 if 'twitter' in IM_MODULES:
     urlpatterns += patterns('astakos.im.target',
-        url(r'^login/twitter/?$', 'twitter.login'),
-        url(r'^login/twitter/authenticated/?$', 'twitter.authenticated')
-    )
+                            url(r'^login/twitter/?$', 'twitter.login'),
+                            url(r'^login/twitter/authenticated/?$',
+                                'twitter.authenticated')
+                            )
+
+urlpatterns += patterns('astakos.im.api',
+                        url(r'^get_services/?$', 'get_services'),
+                        url(r'^get_menu/?$', 'get_menu'),
+                        )
 
 urlpatterns += patterns('astakos.im.api.admin',
-    url(r'^authenticate/?$', 'authenticate_old'),
-    #url(r'^authenticate/v2/?$', 'authenticate'),
-    url(r'^get_services/?$', 'get_services'),
-    url(r'^get_menu/?$', 'get_menu'),
-    url(r'^admin/api/v2.0/users/?$', 'get_user_by_email'),
-    url(r'^admin/api/v2.0/users/(?P<user_id>.+?)/?$', 'get_user_by_username'),
-)
+                        url(r'^authenticate/?$', 'authenticate_old'),
+                        #url(r'^authenticate/v2/?$', 'authenticate'),
+                        url(r'^admin/api/v2.0/users/?$', 'get_user_by_email'),
+                        url(r'^admin/api/v2.0/users/(?P<user_id>.+?)/?$',
+                            'get_user_by_username'),
+                        )
 
 urlpatterns += patterns('astakos.im.api.service',
-    #url(r'^service/api/v2.0/tokens/(?P<token_id>.+?)/?$', 'validate_token'),
-    url(r'^service/api/v2.0/feedback/?$', 'send_feedback'),
-    url(r'^service/api/v2.0/users/?$', 'get_user_by_email'),
-    url(r'^service/api/v2.0/users/(?P<user_id>.+?)/?$', 'get_user_by_username'),
-)
\ No newline at end of file
+                        #url(r'^service/api/v2.0/tokens/(?P<token_id>.+?)/?$', 'validate_token'),
+                        url(r'^service/api/v2.0/feedback/?$', 'send_feedback'),
+                        url(r'^service/api/v2.0/users/?$',
+                            'get_user_by_email'),
+                        url(r'^service/api/v2.0/users/(?P<user_id>.+?)/?$',
+                            'get_user_by_username'),
+                        )
index c27f685..a25a64a 100644 (file)
@@ -1,18 +1,18 @@
 # Copyright 2011-2012 GRNET S.A. All rights reserved.
-# 
+#
 # Redistribution and use in source and binary forms, with or
 # without modification, are permitted provided that the following
 # conditions are met:
-# 
+#
 #   1. Redistributions of source code must retain the above
 #      copyright notice, this list of conditions and the following
 #      disclaimer.
-# 
+#
 #   2. Redistributions in binary form must reproduce the above
 #      copyright notice, this list of conditions and the following
 #      disclaimer in the documentation and/or other materials
 #      provided with the distribution.
-# 
+#
 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
@@ -25,7 +25,7 @@
 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 # POSSIBILITY OF SUCH DAMAGE.
-# 
+#
 # The views and conclusions contained in the software and
 # documentation are those of the authors and should not be
 # interpreted as representing official policies, either expressed
@@ -35,54 +35,58 @@ import logging
 import datetime
 import time
 
-from urllib import quote
-from urlparse import urlsplit, urlunsplit, urlparse
-
+from urlparse import urlparse
 from datetime import tzinfo, timedelta
+
 from django.http import HttpResponse, HttpResponseBadRequest, urlencode
 from django.template import RequestContext
-from django.utils.translation import ugettext as _
 from django.contrib.auth import authenticate
 from django.core.urlresolvers import reverse
-from django.core.exceptions import ValidationError
-from django.contrib.sessions.backends.base import SessionBase
+from django.core.exceptions import ValidationError, ObjectDoesNotExist
+from django.utils.translation import ugettext as _
 
-from astakos.im.models import AstakosUser, Invitation, ApprovalTerms
+from astakos.im.models import AstakosUser, Invitation
 from astakos.im.settings import (
-    INVITATIONS_PER_LEVEL, COOKIE_DOMAIN, FORCE_PROFILE_UPDATE, LOGGING_LEVEL
+    COOKIE_DOMAIN, FORCE_PROFILE_UPDATE
 )
 from astakos.im.functions import login
 
+import astakos.im.messages as astakos_messages
+
 logger = logging.getLogger(__name__)
 
+
 class UTC(tzinfo):
-   def utcoffset(self, dt):
-       return timedelta(0)
+    def utcoffset(self, dt):
+        return timedelta(0)
+
+    def tzname(self, dt):
+        return 'UTC'
 
-   def tzname(self, dt):
-       return 'UTC'
+    def dst(self, dt):
+        return timedelta(0)
 
-   def dst(self, dt):
-       return timedelta(0)
 
 def isoformat(d):
-   """Return an ISO8601 date string that includes a timezone."""
+    """Return an ISO8601 date string that includes a timezone."""
+
+    return d.replace(tzinfo=UTC()).isoformat()
 
-   return d.replace(tzinfo=UTC()).isoformat()
 
 def epoch(datetime):
-    return int(time.mktime(datetime.timetuple())*1000)
+    return int(time.mktime(datetime.timetuple()) * 1000)
+
 
-def get_context(request, extra_context={}, **kwargs):
-    if not extra_context:
-        extra_context = {}
+def get_context(request, extra_context=None, **kwargs):
+    extra_context = extra_context or {}
     extra_context.update(kwargs)
     return RequestContext(request, extra_context)
 
+
 def get_invitation(request):
     """
     Returns the invitation identified by the ``code``.
-    
+
     Raises ValueError if the invitation is consumed or there is another account
     associated with this email.
     """
@@ -91,11 +95,12 @@ def get_invitation(request):
         code = request.POST.get('code')
     if not code:
         return
-    invitation = Invitation.objects.get(code = code)
+    invitation = Invitation.objects.get(code=code)
     if invitation.is_consumed:
-        raise ValueError(_('Invitation is used'))
+        raise ValueError(_(astakos_messages.INVITATION_CONSUMED_ERR))
     if reserved_email(invitation.username):
-        raise ValueError(_('Email: %s is reserved' % invitation.username))
+        email = invitation.username
+        raise ValueError(_(astakos_messages.EMAIL_RESERVED) % locals())
     return invitation
 
 def restrict_next(url, domain=None, allowed_schemes=()):
@@ -148,13 +153,13 @@ def prepare_response(request, user, next='', renew=False):
        as 'X-Auth-User' and 'X-Auth-Token' headers,
        or redirect to the URL provided in 'next'
        with the 'user' and 'token' as parameters.
-       
+
        Reissue the token even if it has not yet
        expired, if the 'renew' parameter is present
        or user has not a valid token.
     """
     renew = renew or (not user.auth_token)
-    renew = renew or (user.auth_token_expires and user.auth_token_expires < datetime.datetime.now())
+    renew = renew or (user.auth_token_expires < datetime.datetime.now())
     if renew:
         user.renew_token(
             flush_sessions=True,
@@ -171,15 +176,15 @@ def prepare_response(request, user, next='', renew=False):
         params = ''
         if next:
             params = '?' + urlencode({'next': next})
-        next = reverse('astakos.im.views.edit_profile') + params
-    
+        next = reverse('edit_profile') + params
+
     response = HttpResponse()
-    
+
     # authenticate before login
     user = authenticate(email=user.email, auth_token=user.auth_token)
     login(request, user)
     request.session.set_expiry(user.auth_token_expires)
-    
+
     if not next:
         next = reverse('astakos.im.views.index')
         
@@ -189,23 +194,76 @@ def prepare_response(request, user, next='', renew=False):
 
 class lazy_string(object):
     def __init__(self, function, *args, **kwargs):
-        self.function=function
-        self.args=args
-        self.kwargs=kwargs
-        
+        self.function = function
+        self.args = args
+        self.kwargs = kwargs
+
     def __str__(self):
         if not hasattr(self, 'str'):
-            self.str=self.function(*self.args, **self.kwargs)
+            self.str = self.function(*self.args, **self.kwargs)
         return self.str
 
+
 def reverse_lazy(*args, **kwargs):
     return lazy_string(reverse, *args, **kwargs)
 
+
 def reserved_email(email):
-    return AstakosUser.objects.filter(email = email).count() != 0
+    return AstakosUser.objects.filter(email__iexact=email).count() != 0
+
 
 def get_query(request):
     try:
         return request.__getattribute__(request.method)
     except AttributeError:
-        return {}
\ No newline at end of file
+        return {}
+
+
+def model_to_dict(obj, exclude=['AutoField', 'ForeignKey', 'OneToOneField'],
+                  include_empty=True):
+    '''
+        serialize model object to dict with related objects
+
+        author: Vadym Zakovinko <vp@zakovinko.com>
+        date: January 31, 2011
+        http://djangosnippets.org/snippets/2342/
+    '''
+    tree = {}
+    for field_name in obj._meta.get_all_field_names():
+        try:
+            field = getattr(obj, field_name)
+        except (ObjectDoesNotExist, AttributeError):
+            continue
+
+        if field.__class__.__name__ in ['RelatedManager', 'ManyRelatedManager']:
+            if field.model.__name__ in exclude:
+                continue
+
+            if field.__class__.__name__ == 'ManyRelatedManager':
+                exclude.append(obj.__class__.__name__)
+            subtree = []
+            for related_obj in getattr(obj, field_name).all():
+                value = model_to_dict(related_obj, exclude=exclude)
+                if value or include_empty:
+                    subtree.append(value)
+            if subtree or include_empty:
+                tree[field_name] = subtree
+            continue
+
+        field = obj._meta.get_field_by_name(field_name)[0]
+        if field.__class__.__name__ in exclude:
+            continue
+
+        if field.__class__.__name__ == 'RelatedObject':
+            exclude.append(field.model.__name__)
+            tree[field_name] = model_to_dict(getattr(obj, field_name),
+                                             exclude=exclude)
+            continue
+
+        value = getattr(obj, field_name)
+        if field.__class__.__name__ == 'ForeignKey':
+            value = unicode(value) if value is not None else value
+        if value or include_empty:
+            tree[field_name] = value
+
+    return tree
index 676798e..ad24a83 100644 (file)
 # or implied, of GRNET S.A.
 
 import logging
-import socket
+import calendar
+import inflect
+
+engine = inflect.engine()
 
-from smtplib import SMTPException
 from urllib import quote
 from functools import wraps
+from datetime import datetime
 
-from django.core.mail import send_mail
-from django.http import (
-    HttpResponse, HttpResponseBadRequest, HttpResponseRedirect
-)
-from django.shortcuts import redirect
-from django.template.loader import render_to_string
-from django.utils.translation import ugettext as _
-from django.core.urlresolvers import reverse
-from django.contrib.auth.decorators import login_required
 from django.contrib import messages
+from django.contrib.auth.decorators import login_required
+from django.core.urlresolvers import reverse
 from django.db import transaction
-from django.utils.http import urlencode
 from django.db.utils import IntegrityError
-from django.contrib.auth.views import password_change
-from django.core.exceptions import ValidationError
-from django.views.decorators.http import require_http_methods
+from django.http import (HttpResponse, HttpResponseBadRequest,
+                         HttpResponseForbidden, HttpResponseRedirect,
+                         HttpResponseBadRequest, Http404)
+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.list_detail import object_list
+from django.core.xheaders import populate_xheaders
 
-from astakos.im.models import AstakosUser, Invitation, ApprovalTerms
+from django.template.loader import render_to_string
+from django.views.decorators.http import require_http_methods
 from astakos.im.activation_backends import get_backend, SimpleBackend
-from astakos.im.util import (
-    get_context, prepare_response, get_query, restrict_next
-)
-from astakos.im.forms import *
-from astakos.im.functions import (send_greeting, send_feedback, SendMailError,
-    invite as invite_func, logout as auth_logout, activate as activate_func,
-    send_activation as send_activation_func
-)
-from astakos.im.settings import (
-    DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, COOKIE_DOMAIN, IM_MODULES,
-    SITENAME, LOGOUT_NEXT, LOGGING_LEVEL
-)
+
+from astakos.im.models import (AstakosUser, ApprovalTerms, AstakosGroup,
+                               EmailChange, GroupKind, Membership,
+                               RESOURCE_SEPARATOR)
+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,
+                              TimelineForm, PickResourceForm,
+                              AstakosGroupCreationSummaryForm)
+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,
+                                  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)
+from astakos.im.tasks import request_billing
+from astakos.im.api.callpoint import AstakosCallpoint
+
+import astakos.im.messages as astakos_messages
 
 logger = logging.getLogger(__name__)
 
+DB_REPLACE_GROUP_SCHEME = """REPLACE(REPLACE("auth_group".name, 'http://', ''),
+                                     'https://', '')"""
+
+callpoint = AstakosCallpoint()
+
 def render_response(template, tab=None, status=200, context_instance=None, **kwargs):
     """
     Calls ``django.template.loader.render_to_string`` with an additional ``tab``
@@ -81,7 +104,8 @@ def render_response(template, tab=None, status=200, context_instance=None, **kwa
     if tab is None:
         tab = template.partition('_')[0].partition('.html')[0]
     kwargs.setdefault('tab', tab)
-    html = render_to_string(template, kwargs, context_instance=context_instance)
+    html = template_loader.render_to_string(
+        template, kwargs, context_instance=context_instance)
     response = HttpResponse(html, status=status)
     return response
 
@@ -100,6 +124,7 @@ def requires_anonymous(func):
         return func(request, *args)
     return wrapper
 
+
 def signed_terms_required(func):
     """
     Decorator checkes whether the request.user is Anonymous and in that case
@@ -107,14 +132,15 @@ def signed_terms_required(func):
     """
     @wraps(func)
     def wrapper(request, *args, **kwargs):
-        if request.user.is_authenticated() and not request.user.signed_terms():
+        if request.user.is_authenticated() and not request.user.signed_terms:
             params = urlencode({'next': request.build_absolute_uri(),
-                              'show_form':''})
+                                'show_form': ''})
             terms_uri = reverse('latest_terms') + '?' + params
             return HttpResponseRedirect(terms_uri)
         return func(request, *args, **kwargs)
     return wrapper
 
+
 @require_http_methods(["GET", "POST"])
 @signed_terms_required
 def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context=None):
@@ -150,6 +176,7 @@ def index(request, login_template_name='im/login.html', profile_template_name='i
         context_instance = get_context(request, extra_context)
     )
 
+
 @require_http_methods(["GET", "POST"])
 @login_required
 @signed_terms_required
@@ -185,50 +212,49 @@ def invite(request, template_name='im/invitations.html', extra_context=None):
     The view expectes the following settings are defined:
 
     * LOGIN_URL: login uri
-    * ASTAKOS_DEFAULT_CONTACT_EMAIL: service support email
-    * ASTAKOS_DEFAULT_FROM_EMAIL: from email
     """
     extra_context = extra_context or {}
     status = None
     message = None
     form = InvitationForm()
-    
+
     inviter = request.user
     if request.method == 'POST':
         form = InvitationForm(request.POST)
         if inviter.invitations > 0:
             if form.is_valid():
                 try:
-                    invitation = form.save()
-                    invite_func(invitation, inviter)
-                    status = messages.SUCCESS
-                    message = _('Invitation sent to %s' % invitation.username)
+                    email = form.cleaned_data.get('username')
+                    realname = form.cleaned_data.get('realname')
+                    inviter.invite(email, realname)
+                    message = _(astakos_messages.INVITATION_SENT) % locals()
+                    messages.success(request, message)
                 except SendMailError, e:
-                    status = messages.ERROR
                     message = e.message
+                    messages.error(request, message)
                     transaction.rollback()
                 except BaseException, e:
-                    status = messages.ERROR
-                    message = _('Something went wrong.')
+                    message = _(astakos_messages.GENERIC_ERROR)
+                    messages.error(request, message)
                     logger.exception(e)
                     transaction.rollback()
                 else:
                     transaction.commit()
         else:
-            status = messages.ERROR
-            message = _('No invitations left')
-    messages.add_message(request, status, message)
+            message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED)
+            messages.error(request, message)
 
     sent = [{'email': inv.username,
              'realname': inv.realname,
              'is_consumed': inv.is_consumed}
-             for inv in request.user.invitations_sent.all()]
+            for inv in request.user.invitations_sent.all()]
     kwargs = {'inviter': inviter,
-              'sent':sent}
+              'sent': sent}
     context = get_context(request, extra_context, **kwargs)
     return render_response(template_name,
-                           invitation_form = form,
-                           context_instance = context)
+                           invitation_form=form,
+                           context_instance=context)
+
 
 @require_http_methods(["GET", "POST"])
 @login_required
@@ -288,18 +314,21 @@ def edit_profile(request, template_name='im/profile.html', extra_context=None):
                 )
                 if next:
                     return redirect(next)
-                msg = _('<p>Profile has been updated successfully</p>')
-                messages.add_message(request, messages.SUCCESS, msg)
+                msg = _(astakos_messages.PROFILE_UPDATED)
+                messages.success(request, msg)
             except ValueError, ve:
-                messages.add_message(request, messages.ERROR, ve)
+                messages.success(request, ve)
     elif request.method == "GET":
-        request.user.is_verified = True
-        request.user.save()
+        if not request.user.is_verified:
+            request.user.is_verified = True
+            request.user.save()
     return render_response(template_name,
                            profile_form = form,
                            context_instance = get_context(request,
                                                           extra_context))
 
+
+@transaction.commit_manually
 @require_http_methods(["GET", "POST"])
 def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
     """
@@ -312,14 +341,14 @@ def signup(request, template_name='im/signup.html', on_success='im/signup_comple
     if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend``
     if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not
     (see activation_backends);
-    
+
     Upon successful user creation, if ``next`` url parameter is present the user is redirected there
     otherwise renders the same page with a success message.
-    
+
     On unsuccessful creation, renders ``template_name`` with an error message.
-    
+
     **Arguments**
-    
+
     ``template_name``
         A custom template to render. This is optional;
         if not specified, this will default to ``im/signup.html``.
@@ -332,14 +361,14 @@ def signup(request, template_name='im/signup.html', on_success='im/signup_comple
         An dictionary of variables to add to the template context.
 
     **Template:**
-    
+
     im/signup.html or ``template_name`` keyword argument.
-    im/signup_complete.html or ``on_success`` keyword argument. 
+    im/signup_complete.html or ``on_success`` keyword argument.
     """
     extra_context = extra_context or {}
     if request.user.is_authenticated():
-        return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
-    
+        return HttpResponseRedirect(reverse('edit_profile'))
+
     provider = get_query(request).get('provider', 'local')
     id = get_query(request).get('id')
     try:
@@ -353,7 +382,7 @@ def signup(request, template_name='im/signup.html', on_success='im/signup_comple
         form = backend.get_signup_form(provider, instance)
     except Exception, e:
         form = SimpleBackend(request).get_signup_form(provider)
-        messages.add_message(request, messages.ERROR, e)
+        messages.error(request, e)
     if request.method == 'POST':
         if form.is_valid():
             user = form.save(commit=False)
@@ -373,7 +402,9 @@ def signup(request, template_name='im/signup.html', on_success='im/signup_comple
                         logger._log(LOGGING_LEVEL, msg, [])
                 if user and user.is_active:
                     next = request.POST.get('next', '')
-                    return prepare_response(request, user, next=next)
+                    response = prepare_response(request, user, next=next)
+                    transaction.commit()
+                    return response
                 messages.add_message(request, status, message)
                 return render_response(
                     on_success,
@@ -386,18 +417,20 @@ def signup(request, template_name='im/signup.html', on_success='im/signup_comple
                 logger.exception(e)
                 status = messages.ERROR
                 message = e.message
-                messages.add_message(request, status, message)
+                messages.error(request, message)
+                transaction.rollback()
             except BaseException, e:
                 logger.exception(e)
-                status = messages.ERROR
-                message = _('Something went wrong.')
-                messages.add_message(request, status, message)
+                message = _(astakos_messages.GENERIC_ERROR)
+                messages.error(request, message)
                 logger.exception(e)
+                transaction.rollback()
     return render_response(template_name,
-                           signup_form = form,
-                           provider = provider,
+                           signup_form=form,
+                           provider=provider,
                            context_instance=get_context(request, extra_context))
 
+
 @require_http_methods(["GET", "POST"])
 @login_required
 @signed_terms_required
@@ -426,7 +459,6 @@ def feedback(request, template_name='im/feedback.html', email_template_name='im/
     **Settings:**
 
     * LOGIN_URL: login uri
-    * ASTAKOS_DEFAULT_CONTACT_EMAIL: List of feedback recipients
     """
     extra_context = extra_context or {}
     if request.method == 'GET':
@@ -442,17 +474,17 @@ def feedback(request, template_name='im/feedback.html', email_template_name='im/
             try:
                 send_feedback(msg, data, request.user, email_template_name)
             except SendMailError, e:
-                message = e.message
-                status = messages.ERROR
+                messages.error(request, message)
             else:
-                message = _('Feedback successfully sent')
-                status = messages.SUCCESS
-            messages.add_message(request, status, message)
+                message = _(astakos_messages.FEEDBACK_SENT)
+                messages.success(request, message)
     return render_response(template_name,
-                           feedback_form = form,
-                           context_instance = get_context(request, extra_context))
+                           feedback_form=form,
+                           context_instance=get_context(request, extra_context))
+
 
 @require_http_methods(["GET"])
+@signed_terms_required
 def logout(request, template='registration/logged_out.html', extra_context=None):
     """
     Wraps `django.contrib.auth.logout`.
@@ -473,14 +505,16 @@ def logout(request, template='registration/logged_out.html', extra_context=None)
         response['Location'] = LOGOUT_NEXT
         response.status_code = 301
     else:
-        messages.add_message(request, messages.SUCCESS, _('<p>You have successfully logged out.</p>'))
+        messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
         context = get_context(request, extra_context)
         response.write(render_to_string(template, context_instance=context))
     return response
 
+
 @require_http_methods(["GET", "POST"])
 @transaction.commit_manually
-def activate(request, greeting_email_template_name='im/welcome_email.txt', helpdesk_email_template_name='im/helpdesk_notification.txt'):
+def activate(request, greeting_email_template_name='im/welcome_email.txt',
+             helpdesk_email_template_name='im/helpdesk_notification.txt'):
     """
     Activates the user identified by the ``auth`` request parameter, sends a welcome email
     and renews the user token.
@@ -493,13 +527,13 @@ def activate(request, greeting_email_template_name='im/welcome_email.txt', helpd
     try:
         user = AstakosUser.objects.get(auth_token=token)
     except AstakosUser.DoesNotExist:
-        return HttpResponseBadRequest(_('No such user'))
-    
+        return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
+
     if user.is_active:
-        message = _('Account already active.')
-        messages.add_message(request, messages.ERROR, message)
+        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
+        messages.error(request, message)
         return index(request)
-    
+
     try:
         activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True)
         response = prepare_response(request, user, next, renew=True)
@@ -512,12 +546,13 @@ def activate(request, greeting_email_template_name='im/welcome_email.txt', helpd
         return index(request)
     except BaseException, e:
         status = messages.ERROR
-        message = _('Something went wrong.')
+        message = _(astakos_messages.GENERIC_ERROR)
         messages.add_message(request, messages.ERROR, message)
         logger.exception(e)
         transaction.rollback()
         return index(request)
 
+
 @require_http_methods(["GET", "POST"])
 def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
     extra_context = extra_context or {}
@@ -530,12 +565,13 @@ def approval_terms(request, term_id=None, template_name='im/approval_terms.html'
             pass
     else:
         try:
-             term = ApprovalTerms.objects.get(id=term_id)
-        except ApprovalTermDoesNotExist, e:
+            term = ApprovalTerms.objects.get(id=term_id)
+        except ApprovalTerms.DoesNotExist, e:
             pass
 
     if not term:
-        return HttpResponseRedirect(reverse('astakos.im.views.index'))
+        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
+        return HttpResponseRedirect(reverse('index'))
     f = open(term.location, 'r')
     terms = f.read()
 
@@ -545,23 +581,24 @@ def approval_terms(request, term_id=None, template_name='im/approval_terms.html'
             domain=COOKIE_DOMAIN
         )
         if not next:
-            next = reverse('astakos.im.views.index')
+            next = reverse('index')
         form = SignApprovalTermsForm(request.POST, instance=request.user)
         if not form.is_valid():
             return render_response(template_name,
-                           terms = terms,
-                           approval_terms_form = form,
-                           context_instance = get_context(request, extra_context))
+                                   terms=terms,
+                                   approval_terms_form=form,
+                                   context_instance=get_context(request, extra_context))
         user = form.save()
         return HttpResponseRedirect(next)
     else:
         form = None
-        if request.user.is_authenticated() and not request.user.signed_terms():
+        if request.user.is_authenticated() and not request.user.signed_terms:
             form = SignApprovalTermsForm(instance=request.user)
         return render_response(template_name,
-                               terms = terms,
-                               approval_terms_form = form,
-                               context_instance = get_context(request, extra_context))
+                               terms=terms,
+                               approval_terms_form=form,
+                               context_instance=get_context(request, extra_context))
+
 
 @require_http_methods(["GET", "POST"])
 @login_required
@@ -577,42 +614,44 @@ def change_email(request, activation_key=None,
         try:
             user = EmailChange.objects.change_email(activation_key)
             if request.user.is_authenticated() and request.user == user:
-                msg = _('Email changed successfully.')
-                messages.add_message(request, messages.SUCCESS, msg)
+                msg = _(astakos_messages.EMAIL_CHANGED)
+                messages.success(request, msg)
                 auth_logout(request)
                 response = prepare_response(request, user)
                 transaction.commit()
                 return response
         except ValueError, e:
-            messages.add_message(request, messages.ERROR, e)
+            messages.error(request, e)
         return render_response(confirm_template_name,
-                               modified_user = user if 'user' in locals() else None,
-                               context_instance = get_context(request,
-                                                              extra_context))
-    
+                               modified_user=user if 'user' in locals(
+                               ) else None,
+                               context_instance=get_context(request,
+                                                            extra_context))
+
     if not request.user.is_authenticated():
         path = quote(request.get_full_path())
-        url = request.build_absolute_uri(reverse('astakos.im.views.index'))
+        url = request.build_absolute_uri(reverse('index'))
         return HttpResponseRedirect(url + '?next=' + path)
     form = EmailChangeForm(request.POST or None)
     if request.method == 'POST' and form.is_valid():
         try:
             ec = form.save(email_template_name, request)
         except SendMailError, e:
-            status = messages.ERROR
             msg = e
+            messages.error(request, msg)
             transaction.rollback()
         except IntegrityError, e:
-            status = messages.ERROR
-            msg = _('There is already a pending change email request.')
+            msg = _(astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
+            messages.error(request, msg)
         else:
-            status = messages.SUCCESS
-            msg = _('Change email request has been registered succefully.\
-                    You are going to receive a verification email in the new address.')
+            msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
+            messages.success(request, msg)
             transaction.commit()
-        messages.add_message(request, status, msg)
-    return render_response(form_template_name,
-                           form = form,)
+    return render_response(
+        form_template_name,
+        form=form,
+        context_instance=get_context(request, extra_context)
+    )
 
 
 def send_activation(request, user_id, template_name='im/login.html', extra_context=None):
@@ -620,20 +659,707 @@ def send_activation(request, user_id, template_name='im/login.html', extra_conte
     try:
         u = AstakosUser.objects.get(id=user_id)
     except AstakosUser.DoesNotExist:
-        messages.error(request, _('Invalid user id'))
+        messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
     else:
         try:
             send_activation_func(u)
-            msg = _('Activation sent.')
+            msg = _(astakos_messages.ACTIVATION_SENT)
             messages.success(request, msg)
         except SendMailError, e:
             messages.error(request, e)
     return render_response(
         template_name,
-        login_form = LoginForm(request=request), 
+        login_form = LoginForm(request=request),
         context_instance = get_context(
             request,
             extra_context
         )
     )
+
+class ResourcePresentation():
+    
+    def __init__(self, data):
+        self.data = data
+        
+    def update_from_result(self, result):
+        if result.is_success:
+            for r in result.data:
+                rname = '%s%s%s' % (r.get('service'), RESOURCE_SEPARATOR, r.get('name'))
+                if not rname in self.data['resources']:
+                    self.data['resources'][rname] = {}
+                    
+                self.data['resources'][rname].update(r)
+                self.data['resources'][rname]['id'] = rname
+                group = r.get('group')
+                if not group in self.data['groups']:
+                    self.data['groups'][group] = {}
+                    
+                self.data['groups'][r.get('group')].update({'name': r.get('group')})
+    
+    def test(self, quota_dict):
+        for k, v in quota_dict.iteritems():
+            rname = k
+            value = v
+            if not rname in self.data['resources']:
+                self.data['resources'][rname] = {}
+                    
+            self.data['resources'][rname]['value'] = value
+            
+    
+    def update_from_result_report(self, result):
+        if result.is_success:
+            for r in result.data:
+                rname = r.get('name')
+                if not rname in self.data['resources']:
+                    self.data['resources'][rname] = {}
+                    
+                self.data['resources'][rname].update(r)
+                self.data['resources'][rname]['id'] = rname
+                group = r.get('group')
+                if not group in self.data['groups']:
+                    self.data['groups'][group] = {}
+                    
+                self.data['groups'][r.get('group')].update({'name': r.get('group')})
+                
+    def get_group_resources(self, group):
+        return dict(filter(lambda t: t[1].get('group') == group, self.data['resources'].iteritems()))
+    
+    def get_groups_resources(self):
+        for g in self.data['groups']:
+            yield g, self.get_group_resources(g)
+    
+    def get_quota(self, group_quotas):
+        for r, v in group_quotas.iteritems():
+            rname = str(r)
+            quota = self.data['resources'].get(rname)
+            quota['value'] = v
+            yield quota
+    
+    
+    def get_policies(self, policies_data):
+        for policy in policies_data:
+            rname = '%s%s%s' % (policy.get('service'), RESOURCE_SEPARATOR, policy.get('resource'))
+            policy.update(self.data['resources'].get(rname))
+            yield policy
+        
+    def __repr__(self):
+        return self.data.__repr__()
+                
+    def __iter__(self, *args, **kwargs):
+        return self.data.__iter__(*args, **kwargs)
+    
+    def __getitem__(self, *args, **kwargs):
+        return self.data.__getitem__(*args, **kwargs)
+    
+    def get(self, *args, **kwargs):
+        return self.data.get(*args, **kwargs)
+        
+        
+
+@require_http_methods(["GET", "POST"])
+@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)
+    
+    if not result.is_success:
+        messages.error(
+            request,
+            'Unable to retrieve system resources: %s' % result.reason
+    )
+    
+    try:
+        kind = GroupKind.objects.get(name=kind_name)
+    except:
+        return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
     
+    
+
+    post_save_redirect = '/im/group/%(id)s/'
+    context_processors = None
+    model, form_class = get_model_and_form_class(
+        model=None,
+        form_class=AstakosGroupCreationForm
+    )
+    
+    if request.method == 'POST':
+        form = form_class(request.POST, request.FILES)
+        if form.is_valid():
+            return render_response(
+                template='im/astakosgroup_form_summary.html',
+                context_instance=get_context(request),
+                form = AstakosGroupCreationSummaryForm(form.cleaned_data),
+                policies = resource_catalog.get_policies(form.policies()),
+                resource_catalog= resource_catalog,
+            )
+         
+    else:
+        now = datetime.now()
+        data = {
+            'kind': kind,
+        }
+        for group, resources in resource_catalog.get_groups_resources():
+            data['is_selected_%s' % group] = False
+            for resource in resources:
+                data['%s_uplimit' % resource] = ''
+        
+        form = form_class(data)
+
+    # Create the template, context, response
+    template_name = "%s/%s_form.html" % (
+        model._meta.app_label,
+        model._meta.object_name.lower()
+    )
+    t = template_loader.get_template(template_name)
+    c = RequestContext(request, {
+        'form': form,
+        'kind': kind,
+        'resource_catalog':resource_catalog,
+    }, context_processors)
+    return HttpResponse(t.render(c))
+
+
+#@require_http_methods(["POST"])
+@require_http_methods(["GET", "POST"])
+@signed_terms_required
+@login_required
+def group_add_complete(request):
+    model = AstakosGroup
+    form = AstakosGroupCreationSummaryForm(request.POST)
+    if form.is_valid():
+        d = form.cleaned_data
+        d['owners'] = [request.user]
+        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(
+                    template_name='im/group_creation_notification.txt',
+                    dictionary={
+                        'group': new_object,
+                        'owner': request.user,
+                        'policies': d.get('policies', [])
+                    }
+                )
+            except SendNotificationError, e:
+                messages.error(request, e, fail_silently=True)
+            post_save_redirect = '/im/group/%(id)s/'
+            return HttpResponseRedirect(post_save_redirect % new_object)
+        else:
+            d = {"verbose_name": model._meta.verbose_name,
+                 "reason":result.reason}
+            msg = _(astakos_messages.OBJECT_CREATED_FAILED) % d 
+            messages.error(request, msg, fail_silently=True)
+    return render_response(
+        template='im/astakosgroup_form_summary.html',
+        context_instance=get_context(request),
+        form=form)
+
+
+#@require_http_methods(["GET"])
+@require_http_methods(["GET", "POST"])
+@signed_terms_required
+@login_required
+def group_list(request):
+    none = request.user.astakos_groups.none()
+    sorting = request.GET.get('sorting')
+    query = """
+        SELECT auth_group.id,
+        %s AS groupname,
+        im_groupkind.name AS kindname,
+        im_astakosgroup.*,
+        owner.email AS groupowner,
+        (SELECT COUNT(*) FROM im_membership
+            WHERE group_id = im_astakosgroup.group_ptr_id
+            AND date_joined IS NOT NULL) AS approved_members_num,
+        (SELECT CASE WHEN(
+                    SELECT date_joined FROM im_membership
+                    WHERE group_id = im_astakosgroup.group_ptr_id
+                    AND person_id = %s) IS NULL
+                    THEN 0 ELSE 1 END) AS membership_status
+        FROM im_astakosgroup
+        INNER JOIN im_membership ON (
+            im_astakosgroup.group_ptr_id = im_membership.group_id)
+        INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
+        INNER JOIN im_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
+        LEFT JOIN im_astakosuser_owner ON (
+            im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
+        LEFT JOIN auth_user as owner ON (
+            im_astakosuser_owner.astakosuser_id = owner.id)
+        WHERE im_membership.person_id = %s 
+        """ % (DB_REPLACE_GROUP_SCHEME, request.user.id, request.user.id)
+       
+    if sorting:
+        query = query+" ORDER BY %s ASC" %sorting    
+    else:
+        query = query+" ORDER BY groupname ASC"     
+    q = AstakosGroup.objects.raw(query)
+
+       
+       
+    # Create the template, context, response
+    template_name = "%s/%s_list.html" % (
+        q.model._meta.app_label,
+        q.model._meta.object_name.lower()
+    )
+    extra_context = dict(
+        is_search=False,
+        q=q,
+        sorting=request.GET.get('sorting'),
+        page=request.GET.get('page', 1)
+    )
+    return render_response(template_name,
+                           context_instance=get_context(request, extra_context)
+    )
+
+
+@require_http_methods(["GET", "POST"])
+@signed_terms_required
+@login_required
+def group_detail(request, group_id):
+    q = AstakosGroup.objects.select_related().filter(pk=group_id)
+    q = q.extra(select={
+        'is_member': """SELECT CASE WHEN EXISTS(
+                            SELECT id FROM im_membership
+                            WHERE group_id = im_astakosgroup.group_ptr_id
+                            AND person_id = %s)
+                        THEN 1 ELSE 0 END""" % request.user.id,
+        'is_owner': """SELECT CASE WHEN EXISTS(
+                        SELECT id FROM im_astakosuser_owner
+                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
+                        AND astakosuser_id = %s)
+                        THEN 1 ELSE 0 END""" % request.user.id,
+        'kindname': """SELECT name FROM im_groupkind
+                       WHERE id = im_astakosgroup.kind_id"""})
+
+    model = q.model
+    context_processors = None
+    mimetype = None
+    try:
+        obj = q.get()
+    except AstakosGroup.DoesNotExist:
+        raise Http404("No %s found matching the query" % (
+            model._meta.verbose_name))
+
+    update_form = AstakosGroupUpdateForm(instance=obj)
+    addmembers_form = AddGroupMembersForm()
+    if request.method == 'POST':
+        update_data = {}
+        addmembers_data = {}
+        for k, v in request.POST.iteritems():
+            if k in update_form.fields:
+                update_data[k] = v
+            if k in addmembers_form.fields:
+                addmembers_data[k] = v
+        update_data = update_data or None
+        addmembers_data = addmembers_data or None
+        update_form = AstakosGroupUpdateForm(update_data, instance=obj)
+        addmembers_form = AddGroupMembersForm(addmembers_data)
+        if update_form.is_valid():
+            update_form.save()
+        if addmembers_form.is_valid():
+            try:
+                map(obj.approve_member, addmembers_form.valid_users)
+            except AssertionError:
+                msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
+                messages.error(request, msg)
+            addmembers_form = AddGroupMembersForm()
+
+    template_name = "%s/%s_detail.html" % (
+        model._meta.app_label, model._meta.object_name.lower())
+    t = template_loader.get_template(template_name)
+    c = RequestContext(request, {
+        'object': obj,
+    }, context_processors)
+
+    # validate sorting
+    sorting = request.GET.get('sorting')
+    if sorting:
+        form = MembersSortForm({'sort_by': sorting})
+        if form.is_valid():
+            sorting = form.cleaned_data.get('sort_by')
+    
+    
+    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 = {'update_form': update_form,
+                     'addmembers_form': addmembers_form,
+                     'page': request.GET.get('page', 1),
+                     'sorting': sorting,
+                     'resource_catalog':resource_catalog,
+                     'quota':resource_catalog.get_quota(obj.quota)}
+    for key, value in extra_context.items():
+        if callable(value):
+            c[key] = value()
+        else:
+            c[key] = value
+    response = HttpResponse(t.render(c), mimetype=mimetype)
+    populate_xheaders(
+        request, response, model, getattr(obj, obj._meta.pk.name))
+    return response
+
+
+@require_http_methods(["GET", "POST"])
+@signed_terms_required
+@login_required
+def group_search(request, extra_context=None, **kwargs):
+    q = request.GET.get('q')
+    sorting = request.GET.get('sorting')
+    if request.method == 'GET':
+        form = AstakosGroupSearchForm({'q': q} if q else None)
+    else:
+        form = AstakosGroupSearchForm(get_query(request))
+        if form.is_valid():
+            q = form.cleaned_data['q'].strip()
+    if q:
+        queryset = AstakosGroup.objects.select_related()
+        queryset = queryset.filter(name__contains=q)
+        queryset = queryset.filter(approval_date__isnull=False)
+        queryset = queryset.extra(select={
+                                  'groupname': DB_REPLACE_GROUP_SCHEME,
+                                  'kindname': "im_groupkind.name",
+                                  'approved_members_num': """
+                    SELECT COUNT(*) FROM im_membership
+                    WHERE group_id = im_astakosgroup.group_ptr_id
+                    AND date_joined IS NOT NULL""",
+                                  'membership_approval_date': """
+                    SELECT date_joined FROM im_membership
+                    WHERE group_id = im_astakosgroup.group_ptr_id
+                    AND person_id = %s""" % request.user.id,
+                                  'is_member': """
+                    SELECT CASE WHEN EXISTS(
+                    SELECT date_joined FROM im_membership
+                    WHERE group_id = im_astakosgroup.group_ptr_id
+                    AND person_id = %s)
+                    THEN 1 ELSE 0 END""" % request.user.id,
+                                  'is_owner': """
+                    SELECT CASE WHEN EXISTS(
+                    SELECT id FROM im_astakosuser_owner
+                    WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
+                    AND astakosuser_id = %s)
+                    THEN 1 ELSE 0 END""" % request.user.id,
+                    'is_owner': """SELECT CASE WHEN EXISTS(
+                        SELECT id FROM im_astakosuser_owner
+                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
+                        AND astakosuser_id = %s)
+                        THEN 1 ELSE 0 END""" % request.user.id, 
+                    })
+        if sorting:
+            # TODO check sorting value
+            queryset = queryset.order_by(sorting)
+        else:
+            queryset = queryset.order_by("groupname")
+
+    else:
+        queryset = AstakosGroup.objects.none()
+    return object_list(
+        request,
+        queryset,
+        paginate_by=PAGINATE_BY,
+        page=request.GET.get('page') or 1,
+        template_name='im/astakosgroup_list.html',
+        extra_context=dict(form=form,
+                           is_search=True,
+                           q=q,
+                           sorting=sorting))
+
+
+@require_http_methods(["GET", "POST"])
+@signed_terms_required
+@login_required
+def group_all(request, extra_context=None, **kwargs):
+    q = AstakosGroup.objects.select_related()
+    q = q.filter(approval_date__isnull=False)
+    q = q.extra(select={
+                'groupname': DB_REPLACE_GROUP_SCHEME,
+                'kindname': "im_groupkind.name",
+                'approved_members_num': """
+                    SELECT COUNT(*) FROM im_membership
+                    WHERE group_id = im_astakosgroup.group_ptr_id
+                    AND date_joined IS NOT NULL""",
+                'membership_approval_date': """
+                    SELECT date_joined FROM im_membership
+                    WHERE group_id = im_astakosgroup.group_ptr_id
+                    AND person_id = %s""" % request.user.id,
+                'is_member': """
+                    SELECT CASE WHEN EXISTS(
+                    SELECT date_joined FROM im_membership
+                    WHERE group_id = im_astakosgroup.group_ptr_id
+                    AND person_id = %s)
+                    THEN 1 ELSE 0 END""" % request.user.id,
+                 'is_owner': """SELECT CASE WHEN EXISTS(
+                        SELECT id FROM im_astakosuser_owner
+                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
+                        AND astakosuser_id = %s)
+                        THEN 1 ELSE 0 END""" % request.user.id,   })
+    sorting = request.GET.get('sorting')
+    if sorting:
+        # TODO check sorting value
+        q = q.order_by(sorting)
+    else:
+        q = q.order_by("groupname")
+        
+    return object_list(
+        request,
+        q,
+        paginate_by=PAGINATE_BY,
+        page=request.GET.get('page') or 1,
+        template_name='im/astakosgroup_list.html',
+        extra_context=dict(form=AstakosGroupSearchForm(),
+                           is_search=True,
+                           sorting=sorting))
+
+
+#@require_http_methods(["POST"])
+@require_http_methods(["POST", "GET"])
+@signed_terms_required
+@login_required
+def group_join(request, group_id):
+    m = Membership(group_id=group_id,
+                   person=request.user,
+                   date_requested=datetime.now())
+    try:
+        m.save()
+        post_save_redirect = reverse(
+            'group_detail',
+            kwargs=dict(group_id=group_id))
+        return HttpResponseRedirect(post_save_redirect)
+    except IntegrityError, e:
+        logger.exception(e)
+        msg = _(astakos_messages.GROUP_JOIN_FAILURE)
+        messages.error(request, msg)
+        return group_search(request)
+
+
+@require_http_methods(["POST"])
+@signed_terms_required
+@login_required
+def group_leave(request, group_id):
+    try:
+        m = Membership.objects.select_related().get(
+            group__id=group_id,
+            person=request.user)
+    except Membership.DoesNotExist:
+        return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
+    if request.user in m.group.owner.all():
+        return HttpResponseForbidden(_(astakos_messages.OWNER_CANNOT_LEAVE_GROUP))
+    return delete_object(
+        request,
+        model=Membership,
+        object_id=m.id,
+        template_name='im/astakosgroup_list.html',
+        post_delete_redirect=reverse(
+            'group_detail',
+            kwargs=dict(group_id=group_id)))
+
+
+def handle_membership(func):
+    @wraps(func)
+    def wrapper(request, group_id, user_id):
+        try:
+            m = Membership.objects.select_related().get(
+                group__id=group_id,
+                person__id=user_id)
+        except Membership.DoesNotExist:
+            return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
+        else:
+            if request.user not in m.group.owner.all():
+                return HttpResponseForbidden(_(astakos_messages.NOT_OWNER))
+            func(request, m)
+            return group_detail(request, group_id)
+    return wrapper
+
+
+#@require_http_methods(["POST"])
+@require_http_methods(["POST", "GET"])
+@signed_terms_required
+@login_required
+@handle_membership
+def approve_member(request, membership):
+    try:
+        membership.approve()
+        realname = membership.person.realname
+        msg = _(astakos_messages.MEMBER_JOINED_GROUP) % locals()
+        messages.success(request, msg)
+    except AssertionError:
+        msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
+        messages.error(request, msg)
+    except BaseException, e:
+        logger.exception(e)
+        realname = membership.person.realname
+        msg = _(astakos_messages.GENERIC_ERROR)
+        messages.error(request, msg)
+
+
+@signed_terms_required
+@login_required
+@handle_membership
+def disapprove_member(request, membership):
+    try:
+        membership.disapprove()
+        realname = membership.person.realname
+        msg = astakos_messages.MEMBER_REMOVED % realname
+        messages.success(request, msg)
+    except BaseException, e:
+        logger.exception(e)
+        msg = _(astakos_messages.GENERIC_ERROR)
+        messages.error(request, msg)
+
+
+#@require_http_methods(["GET"])
+@require_http_methods(["POST", "GET"])
+@signed_terms_required
+@login_required
+def resource_list(request):
+    def with_class(entry):
+        entry['load_class'] = 'red'
+        max_value = float(entry['maxValue'])
+        curr_value = float(entry['currValue'])
+        if max_value > 0 :
+            entry['ratio'] = (curr_value / max_value) * 100
+        else:
+            entry['ratio'] = 0 
+        if entry['ratio'] < 66:
+            entry['load_class'] = 'yellow'
+        if entry['ratio'] < 33:
+            entry['load_class'] = 'green'
+        return entry
+
+    def pluralize(entry):
+        entry['plural'] = engine.plural(entry.get('name'))
+        return entry
+
+    result = callpoint.get_user_status(request.user.id)
+    if result.is_success:
+        backenddata = map(with_class, result.data)
+        data = map(pluralize, result.data)
+    else:
+        data = None
+        messages.error(request, result.reason)
+    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
+    resource_catalog.update_from_result_report(result)
+    
+
+    
+    return render_response('im/resource_list.html',
+                           data=data,
+                           context_instance=get_context(request),
+                           resource_catalog=resource_catalog,
+                           result=result)
+
+
+def group_create_list(request):
+    form = PickResourceForm()
+    return render_response(
+        template='im/astakosgroup_create_list.html',
+        context_instance=get_context(request),)
+
+
+#@require_http_methods(["GET"])
+@require_http_methods(["POST", "GET"])
+@signed_terms_required
+@login_required
+def billing(request):
+
+    today = datetime.today()
+    month_last_day = calendar.monthrange(today.year, today.month)[1]
+    start = request.POST.get('datefrom', None)
+    if start:
+        today = datetime.fromtimestamp(int(start))
+        month_last_day = calendar.monthrange(today.year, today.month)[1]
+
+    start = datetime(today.year, today.month, 1).strftime("%s")
+    end = datetime(today.year, today.month, month_last_day).strftime("%s")
+    r = request_billing.apply(args=('pgerakios@grnet.gr',
+                                    int(start) * 1000,
+                                    int(end) * 1000))
+    data = {}
+
+    try:
+        status, data = r.result
+        data = _clear_billing_data(data)
+        if status != 200:
+            messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
+    except:
+        messages.error(request, r.result)
+
+    return render_response(
+        template='im/billing.html',
+        context_instance=get_context(request),
+        data=data,
+        zerodate=datetime(month=1, year=1970, day=1),
+        today=today,
+        start=int(start),
+        month_last_day=month_last_day)
+
+
+def _clear_billing_data(data):
+
+    # remove addcredits entries
+    def isnotcredit(e):
+        return e['serviceName'] != "addcredits"
+
+    # separate services
+    def servicefilter(service_name):
+        service = service_name
+
+        def fltr(e):
+            return e['serviceName'] == service
+        return fltr
+
+    data['bill_nocredits'] = filter(isnotcredit, data['bill'])
+    data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
+    data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
+    data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
+
+    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
\ No newline at end of file
index 502d9a3..2229ee2 100644 (file)
@@ -36,25 +36,26 @@ import recaptcha.client.captcha as captcha
 from django import forms
 from django.utils.safestring import mark_safe
 from django.utils import simplejson as json
-from django.utils.translation import ugettext as _
 from django.template.loader import render_to_string
 
 from astakos.im.settings import RECAPTCHA_PUBLIC_KEY, RECAPTCHA_OPTIONS, \
-        RECAPTCHA_USE_SSL
+    RECAPTCHA_USE_SSL
+
 
 class RecaptchaWidget(forms.Widget):
     """ A Widget which "renders" the output of captcha.displayhtml """
     def render(self, *args, **kwargs):
         conf = RECAPTCHA_OPTIONS
         recaptcha_conf = ('<script type="text/javascript">'
-                         'var RecaptchaOptions = %s'
-                         '</script>') % json.dumps(conf)
-        custom_widget_html = render_to_string("im/captcha.html", 
- {'conf': 'Bob'})
-        return mark_safe(recaptcha_conf + \
-                         custom_widget_html + \
-                    captcha.displayhtml(RECAPTCHA_PUBLIC_KEY,
-                        use_ssl=RECAPTCHA_USE_SSL))
+                          'var RecaptchaOptions = %s'
+                          '</script>') % json.dumps(conf)
+        custom_widget_html = render_to_string("im/captcha.html",
+                                              {'conf': 'Bob'})
+        return mark_safe(recaptcha_conf +
+                         custom_widget_html +
+                         captcha.displayhtml(RECAPTCHA_PUBLIC_KEY,
+                                             use_ssl=RECAPTCHA_USE_SSL))
+
 
 class DummyWidget(forms.Widget):
     """
@@ -63,6 +64,7 @@ class DummyWidget(forms.Widget):
 
     """
     # make sure that labels are not displayed either
-    is_hidden=True
+    is_hidden = True
+
     def render(self, *args, **kwargs):
         return ''
index 13a03dd..dceee7b 100644 (file)
@@ -35,6 +35,5 @@ from django.conf.urls.defaults import include, patterns
 
 
 urlpatterns = patterns('',
-    (r'^im/', include('astakos.im.urls'))
-)
-
+                       (r'^im/', include('astakos.im.urls'))
+                       )
index 9844d5f..1c72a46 100644 (file)
 #from logging import INFO
 #ASTAKOS_LOGGING_LEVEL = INFO
 
-# Email subjects configuration. For admin/helper notification emails %(user)s 
+# Email subjects configuration. For admin/helper notification emails %(user)s
 # maps to registered/activated user email.
 #ASTAKOS_INVITATION_EMAIL_SUBJECT = 'Invitation to %s alpha2 testing' % SITENAME
 #ASTAKOS_GREETING_EMAIL_SUBJECT = 'Welcome to %s alpha2 testing' % SITENAME
 #ASTAKOS_FEEDBACK_EMAIL_SUBJECT = 'Feedback from %s alpha2 testing' % SITENAME
 #ASTAKOS_VERIFICATION_EMAIL_SUBJECT = '%s alpha2 testing account activation is needed' % SITENAME
-#ASTAKOS_ADMIN_NOTIFICATION_EMAIL_SUBJECT = '%s alpha2 testing account created (%%(user)s)' % SITENAME
+#ASTAKOS_ACCOUNT_CREATION_SUBJECT = '%s alpha2 testing account created (%%(user)s)' % SITENAME)
+#ASTAKOS_GROUP_CREATION_SUBJECT = '%s alpha2 testing group created (%%(group)s)' % SITENAME)
 #ASTAKOS_HELPDESK_NOTIFICATION_EMAIL_SUBJECT = '%s alpha2 testing account activated (%%(user)s)' % SITENAME
 #ASTAKOS_EMAIL_CHANGE_EMAIL_SUBJECT = 'Email change on %s alpha2 testing' % SITENAME
 #ASTAKOS_PASSWORD_RESET_EMAIL_SUBJECT = 'Password reset on %s alpha2 testing' % SITENAME
 
+# Set the quota holder component URI
+#ASTAKOS_QUOTA_HOLDER_URL = ''
+
+# Set the cloud service properties
+# SERVICES = getattr(settings, 'ASTAKOS_SERVICES',
+#                    {'cyclades': {'url':'https://node1.example.com/ui/',
+#                                  'quota': {'vm': 2}},
+#                     'pithos+':  {'url':'https://node2.example.com/ui/',
+#                                  'quota': {'diskspace': 50 * 1024 * 1024 * 1024}}})
+
+# Set the billing URI
+#ASTAKOS_AQUARIUM_URL = ''
+
+# Set how many objects should be displayed per page
+#PAGINATE_BY = getattr(settings, 'ASTAKOS_PAGINATE_BY', 10)
+
 # Enforce token renewal on password change/reset
 # NEWPASSWD_INVALIDATE_TOKEN = getattr(settings, 'ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN', True)
 
diff --git a/snf-astakos-app/distribute-0.6.10-py2.6.egg b/snf-astakos-app/distribute-0.6.10-py2.6.egg
new file mode 100644 (file)
index 0000000..74f827f
Binary files /dev/null and b/snf-astakos-app/distribute-0.6.10-py2.6.egg differ
diff --git a/snf-astakos-app/distribute-0.6.10.tar.gz b/snf-astakos-app/distribute-0.6.10.tar.gz
new file mode 100644 (file)
index 0000000..772bea9
Binary files /dev/null and b/snf-astakos-app/distribute-0.6.10.tar.gz differ
index 10d6684..3539421 100644 (file)
@@ -144,17 +144,17 @@ def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
         except ImportError:
             return _do_download(version, download_base, to_dir, download_delay)
         try:
-            pkg_resources.require("distribute>="+version)
+            pkg_resources.require("distribute>=" + version)
             return
         except pkg_resources.VersionConflict:
             e = sys.exc_info()[1]
             if was_imported:
                 sys.stderr.write(
-                "The required version of distribute (>=%s) is not available,\n"
-                "and can't be installed while this script is running. Please\n"
-                "install a more recent version first, using\n"
-                "'easy_install -U distribute'."
-                "\n\n(Currently using %r)\n" % (version, e.args[0]))
+                    "The required version of distribute (>=%s) is not available,\n"
+                    "and can't be installed while this script is running. Please\n"
+                    "install a more recent version first, using\n"
+                    "'easy_install -U distribute'."
+                    "\n\n(Currently using %r)\n" % (version, e.args[0]))
                 sys.exit(2)
             else:
                 del pkg_resources, sys.modules['pkg_resources']    # reload ok
@@ -167,6 +167,7 @@ def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
         if not no_fake:
             _create_fake_setuptools_pkg_info(to_dir)
 
+
 def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
                         to_dir=os.curdir, delay=15):
     """Download distribute from a specified location and return its filename
@@ -203,6 +204,7 @@ def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
                 dst.close()
     return os.path.realpath(saveto)
 
+
 def _no_sandbox(function):
     def __no_sandbox(*args, **kw):
         try:
@@ -227,6 +229,7 @@ def _no_sandbox(function):
 
     return __no_sandbox
 
+
 def _patch_file(path, content):
     """Will backup the file then patch it"""
     existing_content = open(path).read()
@@ -245,15 +248,18 @@ def _patch_file(path, content):
 
 _patch_file = _no_sandbox(_patch_file)
 
+
 def _same_content(path, content):
     return open(path).read() == content
 
+
 def _rename_path(path):
     new_name = path + '.OLD.%s' % time.time()
     log.warn('Renaming %s into %s', path, new_name)
     os.rename(path, new_name)
     return new_name
 
+
 def _remove_flat_installation(placeholder):
     if not os.path.isdir(placeholder):
         log.warn('Unkown installation at %s', placeholder)
@@ -289,18 +295,20 @@ def _remove_flat_installation(placeholder):
 
 _remove_flat_installation = _no_sandbox(_remove_flat_installation)
 
+
 def _after_install(dist):
     log.warn('After install bootstrap.')
     placeholder = dist.get_command_obj('install').install_purelib
     _create_fake_setuptools_pkg_info(placeholder)
 
+
 def _create_fake_setuptools_pkg_info(placeholder):
     if not placeholder or not os.path.exists(placeholder):
         log.warn('Could not find the install location')
         return
     pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
     setuptools_file = 'setuptools-%s-py%s.egg-info' % \
-            (SETUPTOOLS_FAKED_VERSION, pyver)
+        (SETUPTOOLS_FAKED_VERSION, pyver)
     pkg_info = os.path.join(placeholder, setuptools_file)
     if os.path.exists(pkg_info):
         log.warn('%s already exists', pkg_info)
@@ -321,7 +329,9 @@ def _create_fake_setuptools_pkg_info(placeholder):
     finally:
         f.close()
 
-_create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info)
+_create_fake_setuptools_pkg_info = _no_sandbox(
+    _create_fake_setuptools_pkg_info)
+
 
 def _patch_egg_dir(path):
     # let's check if it's already patched
@@ -343,6 +353,7 @@ def _patch_egg_dir(path):
 
 _patch_egg_dir = _no_sandbox(_patch_egg_dir)
 
+
 def _before_install():
     log.warn('Before install bootstrap.')
     _fake_setuptools()
@@ -351,7 +362,7 @@ def _before_install():
 def _under_prefix(location):
     if 'install' not in sys.argv:
         return True
-    args = sys.argv[sys.argv.index('install')+1:]
+    args = sys.argv[sys.argv.index('install') + 1:]
     for index, arg in enumerate(args):
         for option in ('--root', '--prefix'):
             if arg.startswith('%s=' % option):
@@ -359,7 +370,7 @@ def _under_prefix(location):
                 return location.startswith(top_dir)
             elif arg == option:
                 if len(args) > index:
-                    top_dir = args[index+1]
+                    top_dir = args[index + 1]
                     return location.startswith(top_dir)
         if arg == '--user' and USER_SITE is not None:
             return location.startswith(USER_SITE)
@@ -380,7 +391,8 @@ def _fake_setuptools():
                                   replacement=False))
     except TypeError:
         # old distribute API
-        setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools'))
+        setuptools_dist = ws.find(
+            pkg_resources.Requirement.parse('setuptools'))
 
     if setuptools_dist is None:
         log.warn('No setuptools distribution found')
@@ -406,7 +418,7 @@ def _fake_setuptools():
         log.warn('Egg installation')
         pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
         if (os.path.exists(pkg_info) and
-            _same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
+                _same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
             log.warn('Already patched.')
             return
         log.warn('Patching...')
@@ -448,7 +460,7 @@ def _extractall(self, path=".", members=None):
             # Extract directories with a safe mode.
             directories.append(tarinfo)
             tarinfo = copy.copy(tarinfo)
-            tarinfo.mode = 448 # decimal for oct 0700
+            tarinfo.mode = 448  # decimal for oct 0700
         self.extract(tarinfo, path)
 
     # Reverse sort directories.
index acaaae2..b952913 100644 (file)
@@ -44,7 +44,6 @@ from distutils.util import convert_path
 from setuptools import setup, find_packages
 from astakos import get_version
 
-
 HERE = os.path.abspath(os.path.normpath(os.path.dirname(__file__)))
 try:
     # try to update the version file
@@ -66,11 +65,11 @@ PACKAGES = find_packages(PACKAGES_ROOT)
 
 # Package meta
 CLASSIFIERS = [
-        'Development Status :: 3 - Alpha',
-        'Operating System :: OS Independent',
-        'Programming Language :: Python',
-        'Topic :: Utilities',
-        'License :: OSI Approved :: BSD License',
+    'Development Status :: 3 - Alpha',
+    'Operating System :: OS Independent',
+    'Programming Language :: Python',
+    'Topic :: Utilities',
+    'License :: OSI Approved :: BSD License',
 ]
 
 # Package requirements
@@ -80,7 +79,10 @@ INSTALL_REQUIRES = [
     'httplib2>=0.6.0',
     'snf-common>=0.9.0',
     'recaptcha-client>=1.0.5',
-    'django-ratelimit==0.1'
+    'django-ratelimit==0.1',
+    'celery',
+    'requests',
+    'inflect'
 ]
 
 EXTRAS_REQUIRES = {
@@ -101,13 +103,15 @@ standard_exclude_directories = [
 # Note: you may want to copy this into your setup.py file verbatim, as
 # you can't import this from another package, when you don't know if
 # that package is installed yet.
+
+
 def find_package_data(
     where=".",
     package="",
     exclude=standard_exclude,
     exclude_directories=standard_exclude_directories,
     only_in_packages=True,
-    show_ignored=False):
+        show_ignored=False):
     """
     Return a dictionary suitable for use in ``package_data``
     in a distutils ``setup.py`` file.
@@ -144,7 +148,7 @@ def find_package_data(
                 bad_name = False
                 for pattern in exclude_directories:
                     if (fnmatchcase(name, pattern)
-                        or fn.lower() == pattern.lower()):
+                            or fn.lower() == pattern.lower()):
                         bad_name = True
                         if show_ignored:
                             print >> sys.stderr, (
@@ -154,20 +158,21 @@ def find_package_data(
                 if bad_name:
                     continue
                 if (os.path.isfile(os.path.join(fn, "__init__.py"))
-                    and not prefix):
+                        and not prefix):
                     if not package:
                         new_package = name
                     else:
                         new_package = package + "." + name
                     stack.append((fn, "", new_package, False))
                 else:
-                    stack.append((fn, prefix + name + "/", package, only_in_packages))
+                    stack.append(
+                        (fn, prefix + name + "/", package, only_in_packages))
             elif package or not only_in_packages:
                 # is a file
                 bad_name = False
                 for pattern in exclude:
                     if (fnmatchcase(name, pattern)
-                        or fn.lower() == pattern.lower()):
+                            or fn.lower() == pattern.lower()):
                         bad_name = True
                         if show_ignored:
                             print >> sys.stderr, (
@@ -176,7 +181,7 @@ def find_package_data(
                         break
                 if bad_name:
                     continue
-                out.setdefault(package, []).append(prefix+name)
+                out.setdefault(package, []).append(prefix + name)
     return out
 
 setup(
@@ -184,9 +189,9 @@ setup(
     version=VERSION,
     license='BSD',
     url='http://code.grnet.gr/projects/astakos',
-    description = SHORT_DESCRIPTION,
-    long_description=README + '\n\n' +  CHANGES,
-    classifiers = CLASSIFIERS,
+    description=SHORT_DESCRIPTION,
+    long_description=README + '\n\n' + CHANGES,
+    classifiers=CLASSIFIERS,
     author='GRNET',
     author_email='astakos@grnet.gr',
 
@@ -195,20 +200,19 @@ setup(
     package_data=find_package_data('.'),
     zip_safe=False,
 
-    install_requires = INSTALL_REQUIRES,
+    install_requires=INSTALL_REQUIRES,
 
-    dependency_links = ['http://docs.dev.grnet.gr/pypi'],
+    dependency_links=['http://docs.dev.grnet.gr/pypi'],
 
     entry_points={
         'synnefo': [
-             'default_settings = astakos.im.synnefo_settings',
-             'web_apps = astakos.im.synnefo_settings:installed_apps',
-             'web_middleware = astakos.im.synnefo_settings:middlware_classes',
-             'web_context_processors = astakos.im.synnefo_settings:context_processors',
-             'urls = astakos.urls:urlpatterns',
-             'web_static = astakos.im.synnefo_settings:static_files',
-             'loggers = astakos.im.synnefo_settings:loggers'
+            'default_settings = astakos.im.synnefo_settings',
+            'web_apps = astakos.im.synnefo_settings:installed_apps',
+            'web_middleware = astakos.im.synnefo_settings:middlware_classes',
+            'web_context_processors = astakos.im.synnefo_settings:context_processors',
+            'urls = astakos.urls:urlpatterns',
+            'web_static = astakos.im.synnefo_settings:static_files',
+            'loggers = astakos.im.synnefo_settings:loggers'
         ]
     }
 )
-