Revision 8cf9b2dd

b/snf-astakos-app/astakos/im/functions.py
67 67
from astakos.im.notifications import build_notification, NotificationError
68 68
from astakos.im.models import (
69 69
    AstakosUser, ProjectMembership, ProjectApplication, Project,
70
    sync_projects, PendingMembershipError, get_resource_names, new_chain)
70
    PendingMembershipError, get_resource_names, new_chain)
71 71
from astakos.im.project_notif import (
72 72
    membership_change_notify,
73 73
    application_submit_notify, application_approve_notify,
......
515 515
        raise PermissionDenied(m)
516 516

  
517 517
    membership.accept()
518
    sync_projects()
519 518

  
520 519
    membership_change_notify(project, membership.person, 'accepted')
521 520

  
......
569 568
        raise PermissionDenied(m)
570 569

  
571 570
    membership.remove()
572
    sync_projects()
573 571

  
574 572
    membership_change_notify(project, membership.person, 'removed')
575 573

  
......
585 583
        raise PermissionDenied(m)
586 584

  
587 585
    membership.accept()
588
    sync_projects()
589 586

  
590 587
    # TODO send proper notification
591 588
    return membership
......
608 605
    leave_policy = project.application.member_leave_policy
609 606
    if leave_policy == AUTO_ACCEPT_POLICY:
610 607
        membership.remove()
611
        sync_projects()
612 608
    else:
613 609
        membership.leave_request_date = datetime.now()
614 610
        membership.save()
......
630 626
    if (join_policy == AUTO_ACCEPT_POLICY and
631 627
        not project.violates_members_limit(adding=1)):
632 628
        membership.accept()
633
        sync_projects()
634 629
    return membership
635 630

  
636 631
def submit_application(kw, request_user=None):
......
718 713
        raise PermissionDenied(m)
719 714

  
720 715
    application.approve()
721
    sync_projects()
722

  
723 716
    application_approve_notify(application)
724 717

  
725 718
def check_expiration(execute=False):
......
736 729
    checkAlive(project)
737 730

  
738 731
    project.terminate()
739
    sync_projects()
740 732

  
741 733
    project_termination_notify(project)
742 734

  
......
745 737
    checkAlive(project)
746 738

  
747 739
    project.suspend()
748
    sync_projects()
749 740

  
750 741
    project_suspension_notify(project)
751 742

  
......
757 748
        raise PermissionDenied(m)
758 749

  
759 750
    project.resume()
760
    sync_projects()
761 751

  
762 752
def get_by_chain_or_404(chain_id):
763 753
    try:
b/snf-astakos-app/astakos/im/management/commands/project-admin-checks.py
33 33

  
34 34
from optparse import make_option
35 35
from django.core.management.base import BaseCommand, CommandError
36
from django.db import transaction
37 36

  
38 37
from astakos.im.functions import check_expiration
38
from astakos.im.project_xctx import project_transaction_context
39 39

  
40
@transaction.commit_manually
41 40
class Command(BaseCommand):
42 41
    help = "Perform administration checks on projects"
43 42

  
......
84 83
    def handle(self, *args, **options):
85 84

  
86 85
        execute = options['execute']
86
        if options['expire']:
87
            self.expire(execute=execute)
87 88

  
89
    @project_transaction_context(sync=True)
90
    def expire(self, execute=False, ctx=None):
88 91
        try:
89
            if options['expire']:
90
                projects = check_expiration(execute=execute)
91
                self.print_expired(projects, execute)
92
            projects = check_expiration(execute=execute)
93
            self.print_expired(projects, execute)
92 94
        except BaseException as e:
93
            transaction.rollback()
95
            if ctx:
96
                ctx.mark_rollback()
94 97
            raise CommandError(e)
95
        else:
96
            transaction.commit()
b/snf-astakos-app/astakos/im/management/commands/project-approve.py
34 34
from optparse import make_option
35 35

  
36 36
from django.core.management.base import BaseCommand, CommandError
37
from django.db import transaction
38 37

  
39 38
from astakos.im.models import ProjectApplication
40 39
from astakos.im.functions import approve_application
41
 
40
from astakos.im.project_xctx import project_transaction_context
41

  
42 42
class Command(BaseCommand):
43 43
    args = "<project application id>"
44
    help = "Update project state"
44
    help = "Approve project application"
45 45

  
46
    @transaction.commit_manually
47 46
    def handle(self, *args, **options):
48 47
        if len(args) < 1:
49
            raise CommandError("Please provide a group identifier")
50
        
48
            raise CommandError("Please provide an application id")
49

  
51 50
        try:
52 51
            id = int(args[0])
53 52
        except ValueError:
......
58 57
                app = ProjectApplication.objects.get(id=id)
59 58
            except ProjectApplication.DoesNotExist:
60 59
                raise CommandError('Invalid id')
61
            try:
62
                approve_application(app)
63
            except BaseException, e:
64
                transaction.rollback()
65
                raise CommandError(e)
66
            else:
67
                transaction.commit()
60

  
61
            approve(app)
62

  
63
@project_transaction_context(sync=True)
64
def approve(app, ctx=None):
65
    try:
66
        approve_application(app)
67
    except BaseException as e:
68
        if ctx:
69
            ctx.mark_rollback()
70
            raise CommandError(e)
b/snf-astakos-app/astakos/im/management/commands/project-update.py
34 34
from optparse import make_option
35 35

  
36 36
from django.core.management.base import BaseCommand, CommandError
37
from django.db import transaction
38
from django.views.generic.create_update import lookup_object
39
from django.http import Http404
40

  
41
from astakos.im.models import (
42
    ProjectApplication, Project)
43

  
44 37
from astakos.im.functions import terminate, suspend, resume
38
from astakos.im.project_xctx import project_transaction_context
45 39

  
46
@transaction.commit_manually
47 40
class Command(BaseCommand):
48 41
    args = "<project id>"
49 42
    help = "Update project state"
......
53 46
                    action='store_true',
54 47
                    dest='terminate',
55 48
                    default=False,
56
                    help="Terminate group"),
49
                    help="Terminate project"),
57 50
        make_option('--resume',
58 51
                    action='store_true',
59 52
                    dest='resume',
60 53
                    default=False,
61
                    help="Resume group"),
54
                    help="Resume project"),
62 55
        make_option('--suspend',
63 56
                    action='store_true',
64 57
                    dest='suspend',
65 58
                    default=False,
66
                    help="Suspend group")
59
                    help="Suspend project")
67 60
    )
68 61

  
69 62
    def handle(self, *args, **options):
70 63
        if len(args) < 1:
71
            raise CommandError("Please provide a group identifier")
72
        
64
            raise CommandError("Please provide a project id")
73 65
        try:
74 66
            id = int(args[0])
75 67
        except ValueError:
76 68
            raise CommandError('Invalid id')
77 69
        else:
78
            try:
79
                if options['terminate']:
80
                    terminate(id)
81
                elif options['resume']:
82
                    resume(id)
83
                elif options['suspend']:
84
                    suspend(id)
85
            except BaseException, e:
86
                transaction.rollback()
87
                raise CommandError(e)
88
            else:
89
                transaction.commit()
70
            if options['terminate']:
71
                run_command(terminate, id)
72
            elif options['resume']:
73
                run_command(resume, id)
74
            elif options['suspend']:
75
                run_command(suspend, id)
76

  
77
@project_transaction_context(sync=True)
78
def run_command(func, id, ctx=None):
79
    try:
80
        func(id)
81
    except BaseException as e:
82
        if ctx:
83
            ctx.mark_rollback()
84
        raise CommandError(e)
b/snf-astakos-app/astakos/im/notification_xctx.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 synnefo.lib.db.xctx import TransactionContext, TransactionHandler
35
from astakos.im.notifications import Notification
36

  
37
# USAGE
38
# =====
39
# @notification_transaction_context(notify=False)
40
# def a_view(args, ctx=None):
41
#     ...
42
#     if ctx:
43
#         ctx.mark_rollback()
44
#     ...
45
#     return http response
46
#
47
# OR
48
#
49
# def a_view(args):
50
#     with notification_transaction_context(notify=False) as ctx:
51
#         ...
52
#         ctx.mark_rollback()
53
#         ...
54
#         return http response
55

  
56
def notification_transaction_context(**kwargs):
57
    return TransactionHandler(ctx=NotificationTransactionContext, **kwargs)
58

  
59
class NotificationTransactionContext(TransactionContext):
60
    def __init__(self, notify=True, **kwargs):
61
        self._notifications = []
62
        self._messages      = []
63
        self._notify        = notify
64
        TransactionContext.__init__(self, **kwargs)
65

  
66
    def register(self, o):
67
        if isinstance(o, dict):
68
            msg = o.get('msg', None)
69
            if msg is not None and request is not None:
70
                if isinstance(msg, basestring):
71
                    self.queue_message(msg)
72

  
73
            notif = o.get('notif', None)
74
            fst, snd = o
75
            if isinstance(snd, Notification):
76
                self.queue_notification(snd)
77
                return fst
78
        return o
79

  
80
    def queue_message(m):
81
        self._messages.append(m)
82

  
83
    def queue_notification(self, n):
84
        self._notifications.append(n)
85

  
86
    def _send_notifications(self):
87
        if self._notifications is None:
88
            return
89
        # send mail
90

  
91
    def postprocess(self):
92
        if self._notify:
93
            self._send_notifications()
94
        TransactionContext.postprocess(self)
b/snf-astakos-app/astakos/im/project_xctx.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 synnefo.lib.db.xctx import TransactionHandler
35
from astakos.im.notification_xctx import NotificationTransactionContext
36
from astakos.im.models import sync_projects
37

  
38
# USAGE
39
# =====
40
# @project_transaction_context(sync=True)
41
# def a_view(args, ctx=None):
42
#     ...
43
#     if ctx:
44
#         ctx.mark_rollback()
45
#     ...
46
#     return http response
47
#
48
# OR
49
#
50
# def a_view(args):
51
#     with project_transaction_context(sync=True) as ctx:
52
#         ...
53
#         ctx.mark_rollback()
54
#         ...
55
#         return http response
56

  
57
def project_transaction_context(**kwargs):
58
    return TransactionHandler(ctx=ProjectTransactionContext, **kwargs)
59

  
60
class ProjectTransactionContext(NotificationTransactionContext):
61
    def __init__(self, sync=False, **kwargs):
62
        self._sync = sync
63
        NotificationTransactionContext.__init__(self, **kwargs)
64

  
65
    def postprocess(self):
66
        if self._sync:
67
            sync_projects()
68
        NotificationTransactionContext.postprocess(self)
b/snf-astakos-app/astakos/im/views.py
108 108
from astakos.im import settings as astakos_settings
109 109
from astakos.im.api.callpoint import AstakosCallpoint
110 110
from astakos.im import auth_providers
111
from astakos.im.project_xctx import project_transaction_context
111 112

  
112 113
logger = logging.getLogger(__name__)
113 114

  
......
913 914
        'im/how_it_works.html',
914 915
        context_instance=get_context(request))
915 916

  
916
@transaction.commit_manually
917
@project_transaction_context()
917 918
def _create_object(request, model=None, template_name=None,
918 919
        template_loader=template_loader, extra_context=None, post_save_redirect=None,
919 920
        login_required=False, context_processors=None, form_class=None,
920
        msg=None):
921
        msg=None, ctx=None):
921 922
    """
922 923
    Based of django.views.generic.create_update.create_object which displays a
923 924
    summary page before creating the object.
924 925
    """
925
    rollback = False
926 926
    response = None
927 927

  
928 928
    if extra_context is None: extra_context = {}
......
954 954
    except BaseException, e:
955 955
        logger.exception(e)
956 956
        messages.error(request, _(astakos_messages.GENERIC_ERROR))
957
        rollback = True
957
        if ctx:
958
            ctx.mark_rollback()
958 959
    finally:
959
        if rollback:
960
            transaction.rollback()
961
        else:
962
            transaction.commit()
963

  
964 960
        if response == None:
965 961
            # Create the template, context, response
966 962
            if not template_name:
......
974 970
            response = HttpResponse(t.render(c))
975 971
        return response
976 972

  
977
@transaction.commit_manually
973
@project_transaction_context()
978 974
def _update_object(request, model=None, object_id=None, slug=None,
979 975
        slug_field='slug', template_name=None, template_loader=template_loader,
980 976
        extra_context=None, post_save_redirect=None, login_required=False,
981 977
        context_processors=None, template_object_name='object',
982
        form_class=None, msg=None):
978
        form_class=None, msg=None, ctx=None):
983 979
    """
984 980
    Based of django.views.generic.create_update.update_object which displays a
985 981
    summary page before updating the object.
986 982
    """
987
    rollback = False
988 983
    response = None
989 984

  
990 985
    if extra_context is None: extra_context = {}
......
1017 1012
    except BaseException, e:
1018 1013
        logger.exception(e)
1019 1014
        messages.error(request, _(astakos_messages.GENERIC_ERROR))
1020
        rollback = True
1015
        ctx.mark_rollback()
1021 1016
    finally:
1022
        if rollback:
1023
            transaction.rollback()
1024
        else:
1025
            transaction.commit()
1026 1017
        if response == None:
1027 1018
            if not template_name:
1028 1019
                template_name = "%s/%s_form.html" %\
......
1132 1123
@require_http_methods(["GET", "POST"])
1133 1124
@signed_terms_required
1134 1125
@login_required
1135
@transaction.commit_on_success
1136 1126
def project_app(request, application_id):
1137 1127
    return common_detail(request, application_id, is_chain=False)
1138 1128

  
1139 1129
@require_http_methods(["GET", "POST"])
1140 1130
@signed_terms_required
1141 1131
@login_required
1142
@transaction.commit_on_success
1143 1132
def project_detail(request, chain_id):
1144 1133
    return common_detail(request, chain_id)
1145 1134

  
1146
def addmembers(request, chain_id):
1135
@project_transaction_context(sync=True)
1136
def addmembers(request, chain_id, ctx=None):
1147 1137
    addmembers_form = AddProjectMembersForm(
1148 1138
        request.POST,
1149 1139
        chain_id=int(chain_id),
1150 1140
        request_user=request.user)
1151 1141
    if addmembers_form.is_valid():
1152 1142
        try:
1153
            rollback = False
1154 1143
            chain_id = int(chain_id)
1155 1144
            map(lambda u: enroll_member(
1156 1145
                    chain_id,
......
1160 1149
        except (IOError, PermissionDenied), e:
1161 1150
            messages.error(request, e)
1162 1151
        except BaseException, e:
1163
            rollback = True
1152
            if ctx:
1153
                ctx.mark_rollback()
1164 1154
            messages.error(request, e)
1165
        finally:
1166
            if rollback == True:
1167
                transaction.rollback()
1168
            else:
1169
                transaction.commit()
1170 1155

  
1171 1156
def common_detail(request, chain_or_app_id, is_chain=True):
1172 1157
    if is_chain:
......
1265 1250
@require_http_methods(["POST", "GET"])
1266 1251
@signed_terms_required
1267 1252
@login_required
1268
@transaction.commit_manually
1269
def project_join(request, chain_id):
1253
@project_transaction_context(sync=True)
1254
def project_join(request, chain_id, ctx=None):
1270 1255
    next = request.GET.get('next')
1271 1256
    if not next:
1272 1257
        next = reverse('astakos.im.views.project_detail',
1273 1258
                       args=(chain_id,))
1274 1259

  
1275
    rollback = False
1276 1260
    try:
1277 1261
        chain_id = int(chain_id)
1278 1262
        join_project(chain_id, request.user)
......
1283 1267
    except BaseException, e:
1284 1268
        logger.exception(e)
1285 1269
        messages.error(request, _(astakos_messages.GENERIC_ERROR))
1286
        rollback = True
1287
    finally:
1288
        if rollback:
1289
            transaction.rollback()
1290
        else:
1291
            transaction.commit()
1270
        if ctx:
1271
            ctx.mark_rollback()
1292 1272
    next = restrict_next(next, domain=COOKIE_DOMAIN)
1293 1273
    return redirect(next)
1294 1274

  
1295 1275
@require_http_methods(["POST"])
1296 1276
@signed_terms_required
1297 1277
@login_required
1298
@transaction.commit_manually
1299
def project_leave(request, chain_id):
1278
@project_transaction_context(sync=True)
1279
def project_leave(request, chain_id, ctx=None):
1300 1280
    next = request.GET.get('next')
1301 1281
    if not next:
1302 1282
        next = reverse('astakos.im.views.project_list')
1303 1283

  
1304
    rollback = False
1305 1284
    try:
1306 1285
        chain_id = int(chain_id)
1307 1286
        leave_project(chain_id, request.user)
......
1310 1289
    except BaseException, e:
1311 1290
        logger.exception(e)
1312 1291
        messages.error(request, _(astakos_messages.GENERIC_ERROR))
1313
        rollback = True
1314
    finally:
1315
        if rollback:
1316
            transaction.rollback()
1317
        else:
1318
            transaction.commit()
1319

  
1292
        if ctx:
1293
            ctx.mark_rollback()
1320 1294
    next = restrict_next(next, domain=COOKIE_DOMAIN)
1321 1295
    return redirect(next)
1322 1296

  
1323 1297
@require_http_methods(["POST"])
1324 1298
@signed_terms_required
1325 1299
@login_required
1326
@transaction.commit_manually
1327
def project_cancel(request, chain_id):
1300
@project_transaction_context()
1301
def project_cancel(request, chain_id, ctx=None):
1328 1302
    next = request.GET.get('next')
1329 1303
    if not next:
1330 1304
        next = reverse('astakos.im.views.project_list')
1331 1305

  
1332
    rollback = False
1333 1306
    try:
1334 1307
        chain_id = int(chain_id)
1335 1308
        cancel_membership(chain_id, request.user)
......
1338 1311
    except BaseException, e:
1339 1312
        logger.exception(e)
1340 1313
        messages.error(request, _(astakos_messages.GENERIC_ERROR))
1341
        rollback = True
1342
    finally:
1343
        if rollback:
1344
            transaction.rollback()
1345
        else:
1346
            transaction.commit()
1314
        if ctx:
1315
            ctx.mark_rollback()
1347 1316

  
1348 1317
    next = restrict_next(next, domain=COOKIE_DOMAIN)
1349 1318
    return redirect(next)
......
1351 1320
@require_http_methods(["POST"])
1352 1321
@signed_terms_required
1353 1322
@login_required
1354
@transaction.commit_manually
1355
def project_accept_member(request, chain_id, user_id):
1356
    rollback = False
1323
@project_transaction_context(sync=True)
1324
def project_accept_member(request, chain_id, user_id, ctx=None):
1357 1325
    try:
1358 1326
        chain_id = int(chain_id)
1359 1327
        user_id = int(user_id)
......
1363 1331
    except BaseException, e:
1364 1332
        logger.exception(e)
1365 1333
        messages.error(request, _(astakos_messages.GENERIC_ERROR))
1366
        rollback = True
1334
        if ctx:
1335
            ctx.mark_rollback()
1367 1336
    else:
1368 1337
        realname = m.person.realname
1369 1338
        msg = _(astakos_messages.USER_JOINED_PROJECT) % locals()
1370 1339
        messages.success(request, msg)
1371
    finally:
1372
        if rollback:
1373
            transaction.rollback()
1374
        else:
1375
            transaction.commit()
1376 1340
    return redirect(reverse('project_detail', args=(chain_id,)))
1377 1341

  
1378 1342
@require_http_methods(["POST"])
1379 1343
@signed_terms_required
1380 1344
@login_required
1381
@transaction.commit_manually
1382
def project_remove_member(request, chain_id, user_id):
1383
    rollback = False
1345
@project_transaction_context(sync=True)
1346
def project_remove_member(request, chain_id, user_id, ctx=None):
1384 1347
    try:
1385 1348
        chain_id = int(chain_id)
1386 1349
        user_id = int(user_id)
......
1390 1353
    except BaseException, e:
1391 1354
        logger.exception(e)
1392 1355
        messages.error(request, _(astakos_messages.GENERIC_ERROR))
1393
        rollback = True
1356
        if ctx:
1357
            ctx.mark_rollback()
1394 1358
    else:
1395 1359
        realname = m.person.realname
1396 1360
        msg = _(astakos_messages.USER_LEFT_PROJECT) % locals()
1397 1361
        messages.success(request, msg)
1398
    finally:
1399
        if rollback:
1400
            transaction.rollback()
1401
        else:
1402
            transaction.commit()
1403 1362
    return redirect(reverse('project_detail', args=(chain_id,)))
1404 1363

  
1405 1364
@require_http_methods(["POST"])
1406 1365
@signed_terms_required
1407 1366
@login_required
1408
@transaction.commit_manually
1409
def project_reject_member(request, chain_id, user_id):
1410
    rollback = False
1367
@project_transaction_context()
1368
def project_reject_member(request, chain_id, user_id, ctx=None):
1411 1369
    try:
1412 1370
        chain_id = int(chain_id)
1413 1371
        user_id = int(user_id)
......
1417 1375
    except BaseException, e:
1418 1376
        logger.exception(e)
1419 1377
        messages.error(request, _(astakos_messages.GENERIC_ERROR))
1420
        rollback = True
1378
        if ctx:
1379
            ctx.mark_rollback()
1421 1380
    else:
1422 1381
        realname = m.person.realname
1423 1382
        msg = _(astakos_messages.USER_LEFT_PROJECT) % locals()
1424 1383
        messages.success(request, msg)
1425
    finally:
1426
        if rollback:
1427
            transaction.rollback()
1428
        else:
1429
            transaction.commit()
1430 1384
    return redirect(reverse('project_detail', args=(chain_id,)))
1431 1385

  
1432 1386
def landing(request):

Also available in: Unified diff