Revision 39b2cb50
b/snf-astakos-app/astakos/im/ctx.py | ||
---|---|---|
1 |
# Copyright 2013 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or |
|
4 |
# without modification, are permitted provided that the following |
|
5 |
# conditions are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above |
|
8 |
# copyright notice, this list of conditions and the following |
|
9 |
# disclaimer. |
|
10 |
# |
|
11 |
# 2. Redistributions in binary form must reproduce the above |
|
12 |
# copyright notice, this list of conditions and the following |
|
13 |
# disclaimer in the documentation and/or other materials |
|
14 |
# provided with the distribution. |
|
15 |
# |
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
27 |
# POSSIBILITY OF SUCH DAMAGE. |
|
28 |
# |
|
29 |
# The views and conclusions contained in the software and |
|
30 |
# documentation are those of the authors and should not be |
|
31 |
# interpreted as representing official policies, either expressed |
|
32 |
# or implied, of GRNET S.A. |
|
33 |
|
|
34 |
from django.contrib import messages |
|
35 |
from django.utils.translation import ugettext as _ |
|
36 |
import astakos.im.messages as astakos_messages |
|
37 |
import logging |
|
38 |
|
|
39 |
logger = logging.getLogger(__name__) |
|
40 |
|
|
41 |
|
|
42 |
class ExceptionHandler(object): |
|
43 |
def __init__(self, request): |
|
44 |
self.request = request |
|
45 |
|
|
46 |
def __enter__(self): |
|
47 |
pass |
|
48 |
|
|
49 |
def __exit__(self, type, value, traceback): |
|
50 |
if value is not None: # exception |
|
51 |
logger.exception(value) |
|
52 |
m = _(astakos_messages.GENERIC_ERROR) |
|
53 |
messages.error(self.request, m) |
|
54 |
return True # suppress exception |
b/snf-astakos-app/astakos/im/management/commands/project-control.py | ||
---|---|---|
36 | 36 |
from django.core.management.base import BaseCommand, CommandError |
37 | 37 |
from astakos.im.functions import (terminate, suspend, resume, check_expiration, |
38 | 38 |
approve_application, deny_application) |
39 |
from astakos.im.project_xctx import cmd_project_transaction_context
|
|
39 |
from synnefo.lib.db.transaction import commit_on_success_strict
|
|
40 | 40 |
|
41 | 41 |
|
42 | 42 |
class Command(BaseCommand): |
... | ... | |
117 | 117 |
self.expire(execute=True) |
118 | 118 |
|
119 | 119 |
def run_command(self, func, *args): |
120 |
with cmd_project_transaction_context(sync=True) as ctx: |
|
120 |
@commit_on_success_strict() |
|
121 |
def inner(): |
|
121 | 122 |
try: |
122 | 123 |
func(*args) |
123 | 124 |
except BaseException as e: |
124 |
if ctx: |
|
125 |
ctx.mark_rollback() |
|
126 | 125 |
raise CommandError(e) |
126 |
inner() |
|
127 | 127 |
|
128 | 128 |
def print_expired(self, projects, execute): |
129 | 129 |
length = len(projects) |
... | ... | |
152 | 152 |
self.stdout.write('%d projects have been terminated.\n' % ( |
153 | 153 |
length,)) |
154 | 154 |
|
155 |
@cmd_project_transaction_context(sync=True)
|
|
155 |
@commit_on_success_strict()
|
|
156 | 156 |
def expire(self, execute=False, ctx=None): |
157 | 157 |
try: |
158 | 158 |
projects = check_expiration(execute=execute) |
/dev/null | ||
---|---|---|
1 |
# Copyright 2013 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or |
|
4 |
# without modification, are permitted provided that the following |
|
5 |
# conditions are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above |
|
8 |
# copyright notice, this list of conditions and the following |
|
9 |
# disclaimer. |
|
10 |
# |
|
11 |
# 2. Redistributions in binary form must reproduce the above |
|
12 |
# copyright notice, this list of conditions and the following |
|
13 |
# disclaimer in the documentation and/or other materials |
|
14 |
# provided with the distribution. |
|
15 |
# |
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
27 |
# POSSIBILITY OF SUCH DAMAGE. |
|
28 |
# |
|
29 |
# The views and conclusions contained in the software and |
|
30 |
# documentation are those of the authors and should not be |
|
31 |
# interpreted as representing official policies, either expressed |
|
32 |
# or implied, of GRNET S.A. |
|
33 |
|
|
34 |
from synnefo.lib.db.xctx import TransactionContext |
|
35 |
from astakos.im.retry_xctx import RetryTransactionHandler |
|
36 |
from astakos.im.notifications import Notification |
|
37 |
|
|
38 |
# USAGE |
|
39 |
# ===== |
|
40 |
# @notification_transaction_context(notify=False) |
|
41 |
# def a_view(args, ctx=None): |
|
42 |
# ... |
|
43 |
# if ctx: |
|
44 |
# ctx.mark_rollback() |
|
45 |
# ... |
|
46 |
# return http response |
|
47 |
# |
|
48 |
# OR (more cleanly) |
|
49 |
# |
|
50 |
# def a_view(args): |
|
51 |
# with notification_transaction_context(notify=False) as ctx: |
|
52 |
# ... |
|
53 |
# ctx.mark_rollback() |
|
54 |
# ... |
|
55 |
# return http response |
|
56 |
|
|
57 |
def notification_transaction_context(**kwargs): |
|
58 |
return RetryTransactionHandler(ctx=NotificationTransactionContext, **kwargs) |
|
59 |
|
|
60 |
|
|
61 |
class NotificationTransactionContext(TransactionContext): |
|
62 |
def __init__(self, notify=True, **kwargs): |
|
63 |
self._notifications = [] |
|
64 |
self._messages = [] |
|
65 |
self._notify = notify |
|
66 |
TransactionContext.__init__(self, **kwargs) |
|
67 |
|
|
68 |
def register(self, o): |
|
69 |
if isinstance(o, dict): |
|
70 |
msg = o.get('msg', None) |
|
71 |
if msg is not None: |
|
72 |
if isinstance(msg, basestring): |
|
73 |
self.queue_message(msg) |
|
74 |
|
|
75 |
notif = o.get('notif', None) |
|
76 |
if notif is not None: |
|
77 |
if isinstance(notif, Notification): |
|
78 |
self.queue_notification(notif) |
|
79 |
|
|
80 |
if o.has_key('value'): |
|
81 |
return o['value'] |
|
82 |
return o |
|
83 |
|
|
84 |
def queue_message(self, m): |
|
85 |
self._messages.append(m) |
|
86 |
|
|
87 |
def queue_notification(self, n): |
|
88 |
self._notifications.append(n) |
|
89 |
|
|
90 |
def _send_notifications(self): |
|
91 |
if self._notifications is None: |
|
92 |
return |
|
93 |
# send mail |
|
94 |
|
|
95 |
def postprocess(self): |
|
96 |
if self._notify: |
|
97 |
self._send_notifications() |
|
98 |
TransactionContext.postprocess(self) |
/dev/null | ||
---|---|---|
1 |
# Copyright 2013 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or |
|
4 |
# without modification, are permitted provided that the following |
|
5 |
# conditions are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above |
|
8 |
# copyright notice, this list of conditions and the following |
|
9 |
# disclaimer. |
|
10 |
# |
|
11 |
# 2. Redistributions in binary form must reproduce the above |
|
12 |
# copyright notice, this list of conditions and the following |
|
13 |
# disclaimer in the documentation and/or other materials |
|
14 |
# provided with the distribution. |
|
15 |
# |
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
27 |
# POSSIBILITY OF SUCH DAMAGE. |
|
28 |
# |
|
29 |
# The views and conclusions contained in the software and |
|
30 |
# documentation are those of the authors and should not be |
|
31 |
# interpreted as representing official policies, either expressed |
|
32 |
# or implied, of GRNET S.A. |
|
33 |
|
|
34 |
from astakos.im.retry_xctx import RetryTransactionHandler |
|
35 |
from astakos.im.notification_xctx import NotificationTransactionContext |
|
36 |
from astakos.im.models import sync_projects |
|
37 |
from astakos.im.project_error import project_error_view |
|
38 |
|
|
39 |
# USAGE |
|
40 |
# ===== |
|
41 |
# @project_transaction_context(sync=True) |
|
42 |
# def a_view(args, ctx=None): |
|
43 |
# ... |
|
44 |
# if ctx: |
|
45 |
# ctx.mark_rollback() |
|
46 |
# ... |
|
47 |
# return http response |
|
48 |
# |
|
49 |
# OR (more cleanly) |
|
50 |
# |
|
51 |
# def a_view(args): |
|
52 |
# with project_transaction_context(sync=True) as ctx: |
|
53 |
# ... |
|
54 |
# ctx.mark_rollback() |
|
55 |
# ... |
|
56 |
# return http response |
|
57 |
|
|
58 |
def project_transaction_context(**kwargs): |
|
59 |
return RetryTransactionHandler(ctx=ProjectTransactionContext, |
|
60 |
on_fail=project_error_view, |
|
61 |
**kwargs) |
|
62 |
|
|
63 |
def cmd_project_transaction_context(**kwargs): |
|
64 |
return RetryTransactionHandler(ctx=ProjectTransactionContext, |
|
65 |
**kwargs) |
|
66 |
|
|
67 |
class ProjectTransactionContext(NotificationTransactionContext): |
|
68 |
def __init__(self, sync=False, **kwargs): |
|
69 |
self._sync = sync |
|
70 |
NotificationTransactionContext.__init__(self, **kwargs) |
|
71 |
|
|
72 |
def postprocess(self): |
|
73 |
if self._sync: |
|
74 |
sync_projects() |
|
75 |
NotificationTransactionContext.postprocess(self) |
/dev/null | ||
---|---|---|
1 |
# Copyright 2013 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or |
|
4 |
# without modification, are permitted provided that the following |
|
5 |
# conditions are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above |
|
8 |
# copyright notice, this list of conditions and the following |
|
9 |
# disclaimer. |
|
10 |
# |
|
11 |
# 2. Redistributions in binary form must reproduce the above |
|
12 |
# copyright notice, this list of conditions and the following |
|
13 |
# disclaimer in the documentation and/or other materials |
|
14 |
# provided with the distribution. |
|
15 |
# |
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
27 |
# POSSIBILITY OF SUCH DAMAGE. |
|
28 |
# |
|
29 |
# The views and conclusions contained in the software and |
|
30 |
# documentation are those of the authors and should not be |
|
31 |
# interpreted as representing official policies, either expressed |
|
32 |
# or implied, of GRNET S.A. |
|
33 |
|
|
34 |
from synnefo.lib.db.xctx import TransactionHandler |
|
35 |
from time import sleep |
|
36 |
|
|
37 |
import logging |
|
38 |
logger = logging.getLogger(__name__) |
|
39 |
|
|
40 |
class RetryException(Exception): |
|
41 |
pass |
|
42 |
|
|
43 |
class RetryTransactionHandler(TransactionHandler): |
|
44 |
def __init__(self, retries=3, retry_wait=1.0, on_fail=None, **kwargs): |
|
45 |
self.retries = retries |
|
46 |
self.retry_wait = retry_wait |
|
47 |
self.on_fail = on_fail |
|
48 |
TransactionHandler.__init__(self, **kwargs) |
|
49 |
|
|
50 |
def __call__(self, func): |
|
51 |
def wrap(*args, **kwargs): |
|
52 |
while True: |
|
53 |
try: |
|
54 |
f = TransactionHandler.__call__(self, func) |
|
55 |
return f(*args, **kwargs) |
|
56 |
except RetryException as e: |
|
57 |
self.retries -= 1 |
|
58 |
if self.retries <= 0: |
|
59 |
logger.exception(e) |
|
60 |
f = self.on_fail |
|
61 |
if not callable(f): |
|
62 |
raise |
|
63 |
return f(*args, **kwargs) |
|
64 |
sleep(self.retry_wait) |
|
65 |
except BaseException as e: |
|
66 |
logger.exception(e) |
|
67 |
f = self.on_fail |
|
68 |
if not callable(f): |
|
69 |
raise |
|
70 |
return f(*args, **kwargs) |
|
71 |
return wrap |
b/snf-astakos-app/astakos/im/views.py | ||
---|---|---|
80 | 80 |
AstakosUser, ApprovalTerms, |
81 | 81 |
EmailChange, RESOURCE_SEPARATOR, |
82 | 82 |
AstakosUserAuthProvider, PendingThirdPartyUser, |
83 |
PendingMembershipError, |
|
84 | 83 |
ProjectApplication, ProjectMembership, Project) |
85 | 84 |
from astakos.im.util import ( |
86 | 85 |
get_context, prepare_response, get_query, restrict_next) |
... | ... | |
115 | 114 |
from astakos.im import settings as astakos_settings |
116 | 115 |
from astakos.im.api.callpoint import AstakosCallpoint |
117 | 116 |
from astakos.im import auth_providers as auth |
118 |
from astakos.im.project_xctx import project_transaction_context
|
|
119 |
from astakos.im.retry_xctx import RetryException
|
|
117 |
from synnefo.lib.db.transaction import commit_on_success_strict
|
|
118 |
from astakos.im.ctx import ExceptionHandler
|
|
120 | 119 |
|
121 | 120 |
logger = logging.getLogger(__name__) |
122 | 121 |
|
... | ... | |
931 | 930 |
'im/how_it_works.html', |
932 | 931 |
context_instance=get_context(request)) |
933 | 932 |
|
934 |
@project_transaction_context() |
|
933 |
|
|
934 |
@commit_on_success_strict() |
|
935 | 935 |
def _create_object(request, model=None, template_name=None, |
936 | 936 |
template_loader=template_loader, extra_context=None, post_save_redirect=None, |
937 | 937 |
login_required=False, context_processors=None, form_class=None, |
938 |
msg=None, ctx=None):
|
|
938 |
msg=None): |
|
939 | 939 |
""" |
940 | 940 |
Based of django.views.generic.create_update.create_object which displays a |
941 | 941 |
summary page before creating the object. |
... | ... | |
968 | 968 |
response = redirect(post_save_redirect, new_object) |
969 | 969 |
else: |
970 | 970 |
form = form_class() |
971 |
except BaseException, e: |
|
972 |
logger.exception(e) |
|
973 |
messages.error(request, _(astakos_messages.GENERIC_ERROR)) |
|
974 |
if ctx: |
|
975 |
ctx.mark_rollback() |
|
976 |
finally: |
|
971 |
except (IOError, PermissionDenied), e: |
|
972 |
messages.error(request, e) |
|
973 |
return None |
|
974 |
else: |
|
977 | 975 |
if response == None: |
978 | 976 |
# Create the template, context, response |
979 | 977 |
if not template_name: |
... | ... | |
987 | 985 |
response = HttpResponse(t.render(c)) |
988 | 986 |
return response |
989 | 987 |
|
990 |
@project_transaction_context()
|
|
988 |
@commit_on_success_strict()
|
|
991 | 989 |
def _update_object(request, model=None, object_id=None, slug=None, |
992 | 990 |
slug_field='slug', template_name=None, template_loader=template_loader, |
993 | 991 |
extra_context=None, post_save_redirect=None, login_required=False, |
994 | 992 |
context_processors=None, template_object_name='object', |
995 |
form_class=None, msg=None, ctx=None):
|
|
993 |
form_class=None, msg=None): |
|
996 | 994 |
""" |
997 | 995 |
Based of django.views.generic.create_update.update_object which displays a |
998 | 996 |
summary page before updating the object. |
... | ... | |
1026 | 1024 |
response = redirect(post_save_redirect, obj) |
1027 | 1025 |
else: |
1028 | 1026 |
form = form_class(instance=obj) |
1029 |
except BaseException, e: |
|
1030 |
logger.exception(e) |
|
1031 |
messages.error(request, _(astakos_messages.GENERIC_ERROR)) |
|
1032 |
ctx.mark_rollback() |
|
1033 |
finally: |
|
1027 |
except (IOError, PermissionDenied), e: |
|
1028 |
messages.error(request, e) |
|
1029 |
return None |
|
1030 |
else: |
|
1034 | 1031 |
if response == None: |
1035 | 1032 |
if not template_name: |
1036 | 1033 |
template_name = "%s/%s_form.html" %\ |
... | ... | |
1094 | 1091 |
'show_form':True, |
1095 | 1092 |
'details_fields':details_fields, |
1096 | 1093 |
'membership_fields':membership_fields} |
1097 |
return _create_object( |
|
1098 |
request, |
|
1099 |
template_name='im/projects/projectapplication_form.html', |
|
1100 |
extra_context=extra_context, |
|
1101 |
post_save_redirect=reverse('project_list'), |
|
1102 |
form_class=ProjectApplicationForm, |
|
1103 |
msg=_("The %(verbose_name)s has been received and \ |
|
1104 |
is under consideration.")) |
|
1094 |
|
|
1095 |
response = None |
|
1096 |
with ExceptionHandler(request): |
|
1097 |
response = _create_object( |
|
1098 |
request, |
|
1099 |
template_name='im/projects/projectapplication_form.html', |
|
1100 |
extra_context=extra_context, |
|
1101 |
post_save_redirect=reverse('project_list'), |
|
1102 |
form_class=ProjectApplicationForm, |
|
1103 |
msg=_("The %(verbose_name)s has been received and " |
|
1104 |
"is under consideration."), |
|
1105 |
) |
|
1106 |
|
|
1107 |
if response is not None: |
|
1108 |
return response |
|
1109 |
|
|
1110 |
next = reverse('astakos.im.views.project_list') |
|
1111 |
next = restrict_next(next, domain=COOKIE_DOMAIN) |
|
1112 |
return redirect(next) |
|
1105 | 1113 |
|
1106 | 1114 |
|
1107 | 1115 |
@require_http_methods(["GET"]) |
... | ... | |
1124 | 1132 |
|
1125 | 1133 |
@require_http_methods(["POST"]) |
1126 | 1134 |
@valid_astakos_user_required |
1127 |
@project_transaction_context()
|
|
1128 |
def project_app_cancel(request, application_id, ctx=None):
|
|
1135 |
def project_app_cancel(request, application_id):
|
|
1136 |
next = request.GET.get('next')
|
|
1129 | 1137 |
chain_id = None |
1130 |
try: |
|
1131 |
application_id = int(application_id) |
|
1132 |
chain_id = get_related_project_id(application_id) |
|
1133 |
cancel_application(application_id, request.user) |
|
1134 |
except (IOError, PermissionDenied), e: |
|
1135 |
messages.error(request, e) |
|
1136 |
except BaseException, e: |
|
1137 |
logger.exception(e) |
|
1138 |
messages.error(request, _(astakos_messages.GENERIC_ERROR)) |
|
1139 |
if ctx: |
|
1140 |
ctx.mark_rollback() |
|
1141 |
else: |
|
1142 |
msg = _(astakos_messages.APPLICATION_CANCELLED) |
|
1143 |
messages.success(request, msg) |
|
1144 | 1138 |
|
1145 |
next = request.GET.get('next') |
|
1139 |
with ExceptionHandler(request): |
|
1140 |
chain_id = _project_app_cancel(request, application_id) |
|
1141 |
|
|
1146 | 1142 |
if not next: |
1147 | 1143 |
if chain_id: |
1148 | 1144 |
next = reverse('astakos.im.views.project_detail', args=(chain_id,)) |
... | ... | |
1152 | 1148 |
next = restrict_next(next, domain=COOKIE_DOMAIN) |
1153 | 1149 |
return redirect(next) |
1154 | 1150 |
|
1151 |
@commit_on_success_strict() |
|
1152 |
def _project_app_cancel(request, application_id): |
|
1153 |
chain_id = None |
|
1154 |
try: |
|
1155 |
application_id = int(application_id) |
|
1156 |
chain_id = get_related_project_id(application_id) |
|
1157 |
cancel_application(application_id, request.user) |
|
1158 |
except (IOError, PermissionDenied), e: |
|
1159 |
messages.error(request, e) |
|
1160 |
else: |
|
1161 |
msg = _(astakos_messages.APPLICATION_CANCELLED) |
|
1162 |
messages.success(request, msg) |
|
1163 |
return chain_id |
|
1164 |
|
|
1155 | 1165 |
|
1156 | 1166 |
@require_http_methods(["GET", "POST"]) |
1157 | 1167 |
@valid_astakos_user_required |
... | ... | |
1199 | 1209 |
'details_fields':details_fields, |
1200 | 1210 |
'update_form': True, |
1201 | 1211 |
'membership_fields':membership_fields} |
1202 |
return _update_object( |
|
1203 |
request, |
|
1204 |
object_id=application_id, |
|
1205 |
template_name='im/projects/projectapplication_form.html', |
|
1206 |
extra_context=extra_context, post_save_redirect=reverse('project_list'), |
|
1207 |
form_class=ProjectApplicationForm, |
|
1208 |
msg = _("The %(verbose_name)s has been received and \ |
|
1209 |
is under consideration.")) |
|
1210 | 1212 |
|
1213 |
response = None |
|
1214 |
with ExceptionHandler(request): |
|
1215 |
response =_update_object( |
|
1216 |
request, |
|
1217 |
object_id=application_id, |
|
1218 |
template_name='im/projects/projectapplication_form.html', |
|
1219 |
extra_context=extra_context, post_save_redirect=reverse('project_list'), |
|
1220 |
form_class=ProjectApplicationForm, |
|
1221 |
msg = _("The %(verbose_name)s has been received and " |
|
1222 |
"is under consideration."), |
|
1223 |
) |
|
1224 |
|
|
1225 |
if response is not None: |
|
1226 |
return response |
|
1227 |
|
|
1228 |
next = reverse('astakos.im.views.project_list') |
|
1229 |
next = restrict_next(next, domain=COOKIE_DOMAIN) |
|
1230 |
return redirect(next) |
|
1211 | 1231 |
|
1212 | 1232 |
@require_http_methods(["GET", "POST"]) |
1213 | 1233 |
@valid_astakos_user_required |
... | ... | |
1219 | 1239 |
def project_detail(request, chain_id): |
1220 | 1240 |
return common_detail(request, chain_id) |
1221 | 1241 |
|
1222 |
@project_transaction_context(sync=True)
|
|
1223 |
def addmembers(request, chain_id, addmembers_form, ctx=None):
|
|
1242 |
@commit_on_success_strict()
|
|
1243 |
def addmembers(request, chain_id, addmembers_form): |
|
1224 | 1244 |
if addmembers_form.is_valid(): |
1225 | 1245 |
try: |
1226 | 1246 |
chain_id = int(chain_id) |
... | ... | |
1231 | 1251 |
addmembers_form.valid_users) |
1232 | 1252 |
except (IOError, PermissionDenied), e: |
1233 | 1253 |
messages.error(request, e) |
1234 |
except BaseException, e: |
|
1235 |
if ctx: |
|
1236 |
ctx.mark_rollback() |
|
1237 |
messages.error(request, e) |
|
1238 | 1254 |
|
1239 | 1255 |
def common_detail(request, chain_or_app_id, project_view=True): |
1240 | 1256 |
project = None |
... | ... | |
1245 | 1261 |
request.POST, |
1246 | 1262 |
chain_id=int(chain_id), |
1247 | 1263 |
request_user=request.user) |
1248 |
addmembers(request, chain_id, addmembers_form) |
|
1264 |
with ExceptionHandler(request): |
|
1265 |
addmembers(request, chain_id, addmembers_form) |
|
1266 |
|
|
1249 | 1267 |
if addmembers_form.is_valid(): |
1250 | 1268 |
addmembers_form = AddProjectMembersForm() # clear form data |
1251 | 1269 |
else: |
... | ... | |
1357 | 1375 |
|
1358 | 1376 |
@require_http_methods(["POST"]) |
1359 | 1377 |
@valid_astakos_user_required |
1360 |
@project_transaction_context(sync=True) |
|
1361 |
def project_join(request, chain_id, ctx=None): |
|
1378 |
def project_join(request, chain_id): |
|
1362 | 1379 |
next = request.GET.get('next') |
1363 | 1380 |
if not next: |
1364 | 1381 |
next = reverse('astakos.im.views.project_detail', |
1365 | 1382 |
args=(chain_id,)) |
1366 | 1383 |
|
1384 |
with ExceptionHandler(request): |
|
1385 |
_project_join(request, chain_id) |
|
1386 |
|
|
1387 |
|
|
1388 |
next = restrict_next(next, domain=COOKIE_DOMAIN) |
|
1389 |
return redirect(next) |
|
1390 |
|
|
1391 |
|
|
1392 |
@commit_on_success_strict() |
|
1393 |
def _project_join(request, chain_id): |
|
1367 | 1394 |
try: |
1368 | 1395 |
chain_id = int(chain_id) |
1369 | 1396 |
auto_accepted = join_project(chain_id, request.user) |
... | ... | |
1374 | 1401 |
messages.success(request, m) |
1375 | 1402 |
except (IOError, PermissionDenied), e: |
1376 | 1403 |
messages.error(request, e) |
1377 |
except BaseException, e: |
|
1378 |
logger.exception(e) |
|
1379 |
messages.error(request, _(astakos_messages.GENERIC_ERROR)) |
|
1380 |
if ctx: |
|
1381 |
ctx.mark_rollback() |
|
1382 |
next = restrict_next(next, domain=COOKIE_DOMAIN) |
|
1383 |
return redirect(next) |
|
1404 |
|
|
1384 | 1405 |
|
1385 | 1406 |
@require_http_methods(["POST"]) |
1386 | 1407 |
@valid_astakos_user_required |
1387 |
@project_transaction_context(sync=True) |
|
1388 |
def project_leave(request, chain_id, ctx=None): |
|
1408 |
def project_leave(request, chain_id): |
|
1389 | 1409 |
next = request.GET.get('next') |
1390 | 1410 |
if not next: |
1391 | 1411 |
next = reverse('astakos.im.views.project_list') |
1392 | 1412 |
|
1413 |
with ExceptionHandler(request): |
|
1414 |
_project_leave(request, chain_id) |
|
1415 |
|
|
1416 |
next = restrict_next(next, domain=COOKIE_DOMAIN) |
|
1417 |
return redirect(next) |
|
1418 |
|
|
1419 |
|
|
1420 |
@commit_on_success_strict() |
|
1421 |
def _project_leave(request, chain_id): |
|
1393 | 1422 |
try: |
1394 | 1423 |
chain_id = int(chain_id) |
1395 | 1424 |
auto_accepted = leave_project(chain_id, request.user) |
... | ... | |
1400 | 1429 |
messages.success(request, m) |
1401 | 1430 |
except (IOError, PermissionDenied), e: |
1402 | 1431 |
messages.error(request, e) |
1403 |
except PendingMembershipError as e: |
|
1404 |
raise RetryException() |
|
1405 |
except BaseException, e: |
|
1406 |
logger.exception(e) |
|
1407 |
messages.error(request, _(astakos_messages.GENERIC_ERROR)) |
|
1408 |
if ctx: |
|
1409 |
ctx.mark_rollback() |
|
1410 |
next = restrict_next(next, domain=COOKIE_DOMAIN) |
|
1411 |
return redirect(next) |
|
1432 |
|
|
1412 | 1433 |
|
1413 | 1434 |
@require_http_methods(["POST"]) |
1414 | 1435 |
@valid_astakos_user_required |
1415 |
@project_transaction_context() |
|
1416 |
def project_cancel(request, chain_id, ctx=None): |
|
1436 |
def project_cancel(request, chain_id): |
|
1417 | 1437 |
next = request.GET.get('next') |
1418 | 1438 |
if not next: |
1419 | 1439 |
next = reverse('astakos.im.views.project_list') |
1420 | 1440 |
|
1441 |
with ExceptionHandler(request): |
|
1442 |
_project_cancel(request, chain_id) |
|
1443 |
|
|
1444 |
next = restrict_next(next, domain=COOKIE_DOMAIN) |
|
1445 |
return redirect(next) |
|
1446 |
|
|
1447 |
|
|
1448 |
@commit_on_success_strict() |
|
1449 |
def _project_cancel(request, chain_id): |
|
1421 | 1450 |
try: |
1422 | 1451 |
chain_id = int(chain_id) |
1423 | 1452 |
cancel_membership(chain_id, request.user) |
... | ... | |
1425 | 1454 |
messages.success(request, m) |
1426 | 1455 |
except (IOError, PermissionDenied), e: |
1427 | 1456 |
messages.error(request, e) |
1428 |
except PendingMembershipError as e: |
|
1429 |
raise RetryException() |
|
1430 |
except BaseException, e: |
|
1431 |
logger.exception(e) |
|
1432 |
messages.error(request, _(astakos_messages.GENERIC_ERROR)) |
|
1433 |
if ctx: |
|
1434 |
ctx.mark_rollback() |
|
1435 | 1457 |
|
1436 |
next = restrict_next(next, domain=COOKIE_DOMAIN) |
|
1437 |
return redirect(next) |
|
1438 | 1458 |
|
1439 | 1459 |
@require_http_methods(["POST"]) |
1440 | 1460 |
@valid_astakos_user_required |
1441 |
@project_transaction_context(sync=True) |
|
1442 |
def project_accept_member(request, chain_id, user_id, ctx=None): |
|
1461 |
def project_accept_member(request, chain_id, user_id): |
|
1462 |
|
|
1463 |
with ExceptionHandler(request): |
|
1464 |
_project_accept_member(request, chain_id, user_id) |
|
1465 |
|
|
1466 |
return redirect(reverse('project_detail', args=(chain_id,))) |
|
1467 |
|
|
1468 |
|
|
1469 |
@commit_on_success_strict() |
|
1470 |
def _project_accept_member(request, chain_id, user_id): |
|
1443 | 1471 |
try: |
1444 | 1472 |
chain_id = int(chain_id) |
1445 | 1473 |
user_id = int(user_id) |
1446 | 1474 |
m = accept_membership(chain_id, user_id, request.user) |
1447 | 1475 |
except (IOError, PermissionDenied), e: |
1448 | 1476 |
messages.error(request, e) |
1449 |
except PendingMembershipError as e: |
|
1450 |
raise RetryException() |
|
1451 |
except BaseException, e: |
|
1452 |
logger.exception(e) |
|
1453 |
messages.error(request, _(astakos_messages.GENERIC_ERROR)) |
|
1454 |
if ctx: |
|
1455 |
ctx.mark_rollback() |
|
1456 | 1477 |
else: |
1457 | 1478 |
email = escape(m.person.email) |
1458 | 1479 |
msg = _(astakos_messages.USER_MEMBERSHIP_ACCEPTED) % email |
1459 | 1480 |
messages.success(request, msg) |
1460 |
return redirect(reverse('project_detail', args=(chain_id,))) |
|
1481 |
|
|
1461 | 1482 |
|
1462 | 1483 |
@require_http_methods(["POST"]) |
1463 | 1484 |
@valid_astakos_user_required |
1464 |
@project_transaction_context(sync=True) |
|
1465 |
def project_remove_member(request, chain_id, user_id, ctx=None): |
|
1485 |
def project_remove_member(request, chain_id, user_id): |
|
1486 |
|
|
1487 |
with ExceptionHandler(request): |
|
1488 |
_project_remove_member(request, chain_id, user_id) |
|
1489 |
|
|
1490 |
return redirect(reverse('project_detail', args=(chain_id,))) |
|
1491 |
|
|
1492 |
|
|
1493 |
@commit_on_success_strict() |
|
1494 |
def _project_remove_member(request, chain_id, user_id): |
|
1466 | 1495 |
try: |
1467 | 1496 |
chain_id = int(chain_id) |
1468 | 1497 |
user_id = int(user_id) |
1469 | 1498 |
m = remove_membership(chain_id, user_id, request.user) |
1470 | 1499 |
except (IOError, PermissionDenied), e: |
1471 | 1500 |
messages.error(request, e) |
1472 |
except PendingMembershipError as e: |
|
1473 |
raise RetryException() |
|
1474 |
except BaseException, e: |
|
1475 |
logger.exception(e) |
|
1476 |
messages.error(request, _(astakos_messages.GENERIC_ERROR)) |
|
1477 |
if ctx: |
|
1478 |
ctx.mark_rollback() |
|
1479 | 1501 |
else: |
1480 | 1502 |
email = escape(m.person.email) |
1481 | 1503 |
msg = _(astakos_messages.USER_MEMBERSHIP_REMOVED) % email |
1482 | 1504 |
messages.success(request, msg) |
1483 |
return redirect(reverse('project_detail', args=(chain_id,))) |
|
1505 |
|
|
1484 | 1506 |
|
1485 | 1507 |
@require_http_methods(["POST"]) |
1486 | 1508 |
@valid_astakos_user_required |
1487 |
@project_transaction_context() |
|
1488 |
def project_reject_member(request, chain_id, user_id, ctx=None): |
|
1509 |
def project_reject_member(request, chain_id, user_id): |
|
1510 |
|
|
1511 |
with ExceptionHandler(request): |
|
1512 |
_project_reject_member(request, chain_id, user_id) |
|
1513 |
|
|
1514 |
return redirect(reverse('project_detail', args=(chain_id,))) |
|
1515 |
|
|
1516 |
|
|
1517 |
@commit_on_success_strict() |
|
1518 |
def _project_reject_member(request, chain_id, user_id): |
|
1489 | 1519 |
try: |
1490 | 1520 |
chain_id = int(chain_id) |
1491 | 1521 |
user_id = int(user_id) |
1492 | 1522 |
m = reject_membership(chain_id, user_id, request.user) |
1493 | 1523 |
except (IOError, PermissionDenied), e: |
1494 | 1524 |
messages.error(request, e) |
1495 |
except PendingMembershipError as e: |
|
1496 |
raise RetryException() |
|
1497 |
except BaseException, e: |
|
1498 |
logger.exception(e) |
|
1499 |
messages.error(request, _(astakos_messages.GENERIC_ERROR)) |
|
1500 |
if ctx: |
|
1501 |
ctx.mark_rollback() |
|
1502 | 1525 |
else: |
1503 | 1526 |
email = escape(m.person.email) |
1504 | 1527 |
msg = _(astakos_messages.USER_MEMBERSHIP_REJECTED) % email |
1505 | 1528 |
messages.success(request, msg) |
1506 |
return redirect(reverse('project_detail', args=(chain_id,))) |
|
1529 |
|
|
1507 | 1530 |
|
1508 | 1531 |
@require_http_methods(["POST"]) |
1509 | 1532 |
@signed_terms_required |
1510 | 1533 |
@login_required |
1511 |
@project_transaction_context(sync=True) |
|
1512 |
def project_app_approve(request, application_id, ctx=None): |
|
1534 |
def project_app_approve(request, application_id): |
|
1513 | 1535 |
|
1514 | 1536 |
if not request.user.is_project_admin(): |
1515 | 1537 |
m = _(astakos_messages.NOT_ALLOWED) |
... | ... | |
1520 | 1542 |
except ProjectApplication.DoesNotExist: |
1521 | 1543 |
raise Http404 |
1522 | 1544 |
|
1523 |
approve_application(application_id) |
|
1545 |
with ExceptionHandler(request): |
|
1546 |
_project_app_approve(request, application_id) |
|
1547 |
|
|
1524 | 1548 |
chain_id = get_related_project_id(application_id) |
1525 | 1549 |
return redirect(reverse('project_detail', args=(chain_id,))) |
1526 | 1550 |
|
1551 |
|
|
1552 |
@commit_on_success_strict() |
|
1553 |
def _project_app_approve(request, application_id): |
|
1554 |
approve_application(application_id) |
|
1555 |
|
|
1556 |
|
|
1527 | 1557 |
@require_http_methods(["POST"]) |
1528 | 1558 |
@signed_terms_required |
1529 | 1559 |
@login_required |
1530 |
@project_transaction_context() |
|
1531 |
def project_app_deny(request, application_id, ctx=None): |
|
1560 |
def project_app_deny(request, application_id): |
|
1532 | 1561 |
|
1533 | 1562 |
reason = request.POST.get('reason', None) |
1534 | 1563 |
if not reason: |
... | ... | |
1543 | 1572 |
except ProjectApplication.DoesNotExist: |
1544 | 1573 |
raise Http404 |
1545 | 1574 |
|
1546 |
deny_application(application_id, reason=reason) |
|
1575 |
with ExceptionHandler(request): |
|
1576 |
_project_app_deny(request, application_id, reason) |
|
1577 |
|
|
1547 | 1578 |
return redirect(reverse('project_list')) |
1548 | 1579 |
|
1580 |
|
|
1581 |
@commit_on_success_strict() |
|
1582 |
def _project_app_deny(request, application_id, reason): |
|
1583 |
deny_application(application_id, reason=reason) |
|
1584 |
|
|
1585 |
|
|
1549 | 1586 |
@require_http_methods(["POST"]) |
1550 | 1587 |
@signed_terms_required |
1551 | 1588 |
@login_required |
1552 |
@project_transaction_context() |
|
1553 |
def project_app_dismiss(request, application_id, ctx=None): |
|
1589 |
def project_app_dismiss(request, application_id): |
|
1554 | 1590 |
try: |
1555 | 1591 |
app = ProjectApplication.objects.get(id=application_id) |
1556 | 1592 |
except ProjectApplication.DoesNotExist: |
... | ... | |
1560 | 1596 |
m = _(astakos_messages.NOT_ALLOWED) |
1561 | 1597 |
raise PermissionDenied(m) |
1562 | 1598 |
|
1563 |
# XXX: dismiss application also does authorization
|
|
1564 |
dismiss_application(application_id, request_user=request.user)
|
|
1599 |
with ExceptionHandler(request):
|
|
1600 |
_project_app_dismiss(request, application_id)
|
|
1565 | 1601 |
|
1566 | 1602 |
chain_id = None |
1567 | 1603 |
chain_id = get_related_project_id(application_id) |
... | ... | |
1571 | 1607 |
next = reverse('project_list') |
1572 | 1608 |
return redirect(next) |
1573 | 1609 |
|
1610 |
|
|
1611 |
def _project_app_dismiss(request, application_id): |
|
1612 |
# XXX: dismiss application also does authorization |
|
1613 |
dismiss_application(application_id, request_user=request.user) |
|
1614 |
|
|
1615 |
|
|
1574 | 1616 |
@require_http_methods(["GET"]) |
1575 | 1617 |
@required_auth_methods_assigned(allow_access=True) |
1576 | 1618 |
@login_required |
b/snf-common/synnefo/lib/db/transaction.py | ||
---|---|---|
1 |
# Copyright 2013 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or |
|
4 |
# without modification, are permitted provided that the following |
|
5 |
# conditions are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above |
|
8 |
# copyright notice, this list of conditions and the following |
|
9 |
# disclaimer. |
|
10 |
# |
|
11 |
# 2. Redistributions in binary form must reproduce the above |
|
12 |
# copyright notice, this list of conditions and the following |
|
13 |
# disclaimer in the documentation and/or other materials |
|
14 |
# provided with the distribution. |
|
15 |
# |
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
27 |
# POSSIBILITY OF SUCH DAMAGE. |
|
28 |
# |
|
29 |
# The views and conclusions contained in the software and |
|
30 |
# documentation are those of the authors and should not be |
|
31 |
# interpreted as representing official policies, either expressed |
|
32 |
# or implied, of GRNET S.A. |
|
33 |
|
|
34 |
|
|
35 |
from django.db import transaction |
|
36 |
import logging |
|
37 |
|
|
38 |
logger = logging.getLogger(__name__) |
|
39 |
|
|
40 |
|
|
41 |
def commit_on_success_strict(**kwargs): |
|
42 |
def wrap(func): |
|
43 |
@transaction.commit_manually(**kwargs) |
|
44 |
def inner(*args, **kwargs): |
|
45 |
try: |
|
46 |
result = func(*args, **kwargs) |
|
47 |
transaction.commit() |
|
48 |
return result |
|
49 |
except BaseException as e: |
|
50 |
logger.exception(e) |
|
51 |
transaction.rollback() |
|
52 |
raise |
|
53 |
return inner |
|
54 |
return wrap |
/dev/null | ||
---|---|---|
1 |
# Copyright 2013 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or |
|
4 |
# without modification, are permitted provided that the following |
|
5 |
# conditions are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above |
|
8 |
# copyright notice, this list of conditions and the following |
|
9 |
# disclaimer. |
|
10 |
# |
|
11 |
# 2. Redistributions in binary form must reproduce the above |
|
12 |
# copyright notice, this list of conditions and the following |
|
13 |
# disclaimer in the documentation and/or other materials |
|
14 |
# provided with the distribution. |
|
15 |
# |
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
27 |
# POSSIBILITY OF SUCH DAMAGE. |
|
28 |
# |
|
29 |
# The views and conclusions contained in the software and |
|
30 |
# documentation are those of the authors and should not be |
|
31 |
# interpreted as representing official policies, either expressed |
|
32 |
# or implied, of GRNET S.A. |
|
33 |
|
|
34 |
from django.db import transaction |
|
35 |
|
|
36 |
# USAGE |
|
37 |
# ===== |
|
38 |
# @transaction_context() |
|
39 |
# def a_view(args, ctx=None): |
|
40 |
# ... |
|
41 |
# if ctx: |
|
42 |
# ctx.mark_rollback() |
|
43 |
# ... |
|
44 |
# return http response |
|
45 |
# |
|
46 |
# OR (more cleanly) |
|
47 |
# |
|
48 |
# def a_view(args): |
|
49 |
# with transaction_context() as ctx: |
|
50 |
# ... |
|
51 |
# ctx.mark_rollback() |
|
52 |
# ... |
|
53 |
# return http response |
|
54 |
|
|
55 |
def transaction_context(**kwargs): |
|
56 |
return TransactionHandler(ctx=TransactionContext, **kwargs) |
|
57 |
|
|
58 |
|
|
59 |
class TransactionContext(object): |
|
60 |
def __init__(self, **kwargs): |
|
61 |
self._rollback = False |
|
62 |
|
|
63 |
def mark_rollback(self): |
|
64 |
self._rollback = True |
|
65 |
|
|
66 |
def is_marked_rollback(self): |
|
67 |
return self._rollback |
|
68 |
|
|
69 |
def postprocess(self): |
|
70 |
pass |
|
71 |
|
|
72 |
|
|
73 |
class TransactionHandler(object): |
|
74 |
def __init__(self, ctx=None, allow_postprocess=True, using=None, **kwargs): |
|
75 |
self.using = using |
|
76 |
self.db = (using if using is not None |
|
77 |
else transaction.DEFAULT_DB_ALIAS) |
|
78 |
self.ctx_class = ctx |
|
79 |
self.ctx_kwargs = kwargs |
|
80 |
self.allow_postprocess = allow_postprocess |
|
81 |
|
|
82 |
def __call__(self, func): |
|
83 |
def wrap(*args, **kwargs): |
|
84 |
with self as ctx: |
|
85 |
kwargs['ctx'] = ctx |
|
86 |
return func(*args, **kwargs) |
|
87 |
return wrap |
|
88 |
|
|
89 |
def __enter__(self): |
|
90 |
db = self.db |
|
91 |
transaction.enter_transaction_management(using=db) |
|
92 |
transaction.managed(True, using=db) |
|
93 |
self.ctx = self.ctx_class(self.ctx_kwargs) |
|
94 |
return self.ctx |
|
95 |
|
|
96 |
def __exit__(self, type, value, traceback): |
|
97 |
db = self.db |
|
98 |
trigger_postprocess = False |
|
99 |
try: |
|
100 |
if value is not None: # exception |
|
101 |
if transaction.is_dirty(using=db) or True: |
|
102 |
# Rollback, even if is not dirty. |
|
103 |
# This is a temporary bug fix for |
|
104 |
# https://code.djangoproject.com/ticket/9964 . |
|
105 |
# Django prior to 1.3 does not set a transaction |
|
106 |
# dirty when the DB throws an exception, and thus |
|
107 |
# does not trigger rollback, resulting in a |
|
108 |
# dangling aborted DB transaction. |
|
109 |
transaction.rollback(using=db) |
|
110 |
else: |
|
111 |
if transaction.is_dirty(using=db): |
|
112 |
if self.ctx.is_marked_rollback(): |
|
113 |
transaction.rollback(using=db) |
|
114 |
else: |
|
115 |
try: |
|
116 |
transaction.commit(using=db) |
|
117 |
except: |
|
118 |
transaction.rollback(using=db) |
|
119 |
raise |
|
120 |
else: |
|
121 |
trigger_postprocess = True |
|
122 |
|
|
123 |
# postprocess, |
|
124 |
# even if there was nothing to commit |
|
125 |
# as long as it's not marked for rollback |
|
126 |
elif not self.ctx.is_marked_rollback(): |
|
127 |
trigger_postprocess = True |
|
128 |
finally: |
|
129 |
transaction.leave_transaction_management(using=db) |
|
130 |
|
|
131 |
# checking allow_postprocess is needed |
|
132 |
# in order to avoid endless recursion |
|
133 |
if trigger_postprocess and self.allow_postprocess: |
|
134 |
with TransactionHandler(ctx=self.ctx_class, |
|
135 |
allow_postprocess=False, |
|
136 |
using=self.using, |
|
137 |
**self.ctx_kwargs): |
|
138 |
self.ctx.postprocess() |
Also available in: Unified diff