views.py 15.5 KB
Newer Older
André Anjos's avatar
André Anjos committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :

###############################################################################
#                                                                             #
# Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/           #
# Contact: beat.support@idiap.ch                                              #
#                                                                             #
# This file is part of the beat.web module of the BEAT platform.              #
#                                                                             #
# Commercial License Usage                                                    #
# Licensees holding valid commercial BEAT licenses may use this file in       #
# accordance with the terms contained in a written agreement between you      #
# and Idiap. For further information contact tto@idiap.ch                     #
#                                                                             #
# Alternatively, this file may be used under the terms of the GNU Affero      #
# Public License version 3 as published by the Free Software and appearing    #
# in the file LICENSE.AGPL included in the packaging of this file.            #
# The BEAT platform is distributed in the hope that it will be useful, but    #
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY  #
# or FITNESS FOR A PARTICULAR PURPOSE.                                        #
#                                                                             #
# You should have received a copy of the GNU Affero Public License along      #
# with the BEAT platform. If not, see http://www.gnu.org/licenses/.           #
#                                                                             #
###############################################################################


from django.shortcuts import get_object_or_404
30
from django.shortcuts import render
31
from django.template import loader
André Anjos's avatar
André Anjos committed
32 33 34 35 36
from django.contrib.auth.views import login as django_login
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.models import User
from django.contrib import messages
from django.http import HttpResponseRedirect, HttpResponse
37
from django.http import Http404
André Anjos's avatar
André Anjos committed
38 39 40 41 42 43 44 45 46 47 48
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
from django.db.models import Q
from django.core.mail import EmailMessage
from django.template.loader import render_to_string
from django.conf import settings

from rest_framework.authtoken.models import Token

from ..import __version__
from ..common.models import Shareable
49
from ..accounts.models import Profile, SupervisionTrack
50 51
from ..utils import mail

Samuel GAIST's avatar
Samuel GAIST committed
52
from .registration.forms import BlockedUserRevalidationForm
53 54

import datetime
55 56 57 58
try:
    from urlparse import urlparse
except ImportError:
    from urllib.parse import urlparse
André Anjos's avatar
André Anjos committed
59 60 61 62 63

import logging
logger = logging.getLogger(__name__)


64 65 66
#----------------------------------------------------------


André Anjos's avatar
André Anjos committed
67 68 69
def index(request):
    '''Our main index page'''

70
    return render(request, 'ui/index.html')
André Anjos's avatar
André Anjos committed
71 72


73
#----------------------------------------------------------
André Anjos's avatar
André Anjos committed
74 75 76 77 78 79 80


def login(request):
    '''Login page'''

    response = django_login(request)
    if request.user.is_authenticated():
81 82 83 84 85
        path = request.GET.get('next', '/')
        if path in ('/', reverse('login')):
            return HttpResponseRedirect(reverse('activity-stream', args=[request.user.username]))
        else:
            return HttpResponseRedirect(path)
André Anjos's avatar
André Anjos committed
86 87 88 89

    return response


90 91 92
#----------------------------------------------------------


93 94 95 96 97 98 99 100 101 102 103
def blocked_user_reactivation(request):
    '''Reactivation page'''

    if request.method == "POST":
        form = BlockedUserRevalidationForm(request.POST)
        if form.is_valid():
            try:
                user = User.objects.get(username=request.POST["username"])
                if user.check_password(request.POST["password"]):
                    # Check if user is a blocked user
                    if user.profile.status == Profile.BLOCKED:
104 105 106
                        supervisor = User.objects.get(username=request.POST["supervisor"])
                        # Check the supervisor
                        if supervisor.profile.status == Profile.ACCEPTED:
107 108 109 110 111 112 113
                            # Check if supervision track already exists
                            if user.profile.supervision_key is None:
                                # Create and assign key
                                supervisee = user
                                supervisee.profile.supervision_key = supervisee.profile._generate_current_supervision_key()
                                supervisiontrack = SupervisionTrack.objects.create(
                                    supervisee = supervisee,
114
                                    supervisor = supervisor,
115
                                    is_valid = False,
116
                                )
117 118 119 120 121 122 123

                                # Assign key to supervision track
                                supervisiontrack.supervision_key = supervisee.profile.supervision_key
                                supervisiontrack.save()
                                supervisee.profile.supervision.add(supervisiontrack)
                                # Add a rejection date to the supervisee profile
                                now = datetime.datetime.now()
124
                                expiration_date_delta = datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS_FROM_SUPERVISOR)
125 126 127 128 129 130 131 132 133 134
                                if supervisee.profile.rejection_date == None:
                                    supervisee.profile.rejection_date = now + expiration_date_delta

                                supervisee.profile.save()
                                supervisee.save()

                                #Inform by email the supervisor that he has a new supervisee request
                                parsed_url = urlparse(settings.URL_PREFIX)
                                server_address = '%s://%s' % (parsed_url.scheme, parsed_url.hostname)

135 136 137 138 139
                                context = {
                                    'supervisor': supervisor,
                                    'supervisee': supervisee,
                                    'prefix': server_address,
                                }
140

141 142 143 144
                                mail.send_email('registration/mail.supervisor_validation.subject.txt',
                                                'registration/mail.supervisor_validation_supervisee_add_request.message.txt',
                                                context,
                                                [supervisor.email])
145 146

                                # inform the supervisee of his request
147 148 149 150
                                mail.send_email('registration/mail.supervisee_blocked_validation_wait.subject.txt',
                                                'registration/mail.supervisee_blocked_state_wait_for_activation.message.txt',
                                                context,
                                                [supervisee.email])
151 152 153 154 155

                                messages.success(request, "Your supervision request has been successfully processed.")
                            else:
                                messages.error(request, "A supervision request already exists for this account, you need to wait for your supervisor's decision.")
                        else:
156
                            messages.error(request, "The selected supervisor is not valid.")
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
                    else:
                        path = request.GET.get('next', '/')
                        messages.error(request, "Your profile is not blocked, you can go to the login page instead.")
                else:
                    # Don't give too much details though we know the problem is the password only at this step
                    messages.error(request, "Wrong user or password combination!")
            except User.DoesNotExist:
                # Don't give too much details though we know the problem is the password only at this step
                messages.error(request, "Wrong user or password combination!")
                pass

            #return redirect('blocked_user_reactivation', pk=post.pk)
    else:
        form = BlockedUserRevalidationForm()

172 173 174
    return render(request,
                  'registration/blocked_user_reactivate.html',
                  {'form': form})
175 176


177
#----------------------------------------------------------
178 179


André Anjos's avatar
André Anjos committed
180 181 182 183 184 185 186 187 188 189 190
def gather_contributions(requestor, author):
    '''Gather contributions that are accessible to a certain requestor'''

    from ..experiments.models import Experiment
    from ..toolchains.models import Toolchain
    from ..algorithms.models import Algorithm
    from ..libraries.models import Library
    from ..dataformats.models import DataFormat
    from ..team.models import Team
    from ..attestations.models import Attestation
    from ..reports.models import Report
191
    from ..plotters.models import Plotter
André Anjos's avatar
André Anjos committed
192 193 194 195 196 197 198 199
    from ..search.models import Search

    experiments = Experiment.objects.for_user(requestor).filter(author=author)
    toolchains = Toolchain.objects.for_user(requestor).filter(author=author)
    algorithms = Algorithm.objects.for_user(requestor).filter(author=author)
    libraries = Library.objects.for_user(requestor).filter(author=author)
    dataformats = DataFormat.objects.for_user(requestor).filter(author=author)
    teams = Team.objects.for_user(requestor).filter(owner=author)
200
    plotters = Plotter.objects.for_user(requestor).filter(author=author)
André Anjos's avatar
André Anjos committed
201 202 203 204 205 206 207 208 209 210 211 212
    if requestor == author:
      attestations = Attestation.objects.filter(experiment__author=author)
    else:
      attestations = Attestation.objects.published().filter(experiment__author=author)
    searches = Search.objects.for_user(requestor).filter(author=author)
    if requestor == author:
      reports = Report.objects.filter(author=author)
    else:
      reports = Report.objects.published().filter(author=author)

    return dict(

213 214 215 216 217 218
        experiments=experiments,
        toolchains=toolchains,
        algorithms=algorithms,
        libraries=libraries,
        dataformats=dataformats,
        teams=teams,
André Anjos's avatar
André Anjos committed
219

220 221 222 223
        attestations=attestations,
        searches=searches,
        reports=reports,
        plotters=plotters,
André Anjos's avatar
André Anjos committed
224

225
    )
André Anjos's avatar
André Anjos committed
226 227


228
#----------------------------------------------------------
André Anjos's avatar
André Anjos committed
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245


def activity_stream(request, author_name):
    '''User-specific activity stream'''

    # check that the user exists on the system
    author = get_object_or_404(User, username=author_name)

    # gather leaderboards for the following conditions:
    from ..search.models import Search, Leaderboard
    if request.user == author:
      # 1. request.user == author AND user is subscribed
      leaderboards = Leaderboard.objects.filter(search__in=Search.objects.for_user(author, add_public=True), notify__in=(author,)).order_by('-changed')
    else:
      # 2. request.user != author
      leaderboards = Leaderboard.objects.filter(search__in=Search.objects.for_user(request.user).filter(author=author)).order_by('-changed')

246 247 248 249 250 251 252 253
    return render(request,
                  'ui/activity_stream.html',
                  dict(
                      owner = (request.user == author),
                      author= author,
                      statistics= gather_contributions(request.user, author),
                      leaderboards= leaderboards,
                  ))
André Anjos's avatar
André Anjos committed
254 255


256
#----------------------------------------------------------
André Anjos's avatar
André Anjos committed
257 258 259 260 261 262 263 264 265 266 267 268 269 270


@login_required
def docreq(request, author_name):
    '''Request a documentation for an existing platform object'''

    url = request.META.get('HTTP_REFERER')
    if not url: raise Http404()
    if author_name not in url: raise Http404()

    author = get_object_or_404(User, username=author_name)

    to = '%s %s <%s>' % (author.first_name, author.last_name, author.email)
    cc = '%s %s <%s>' % (request.user.first_name, request.user.last_name,
271
                         request.user.email)
André Anjos's avatar
André Anjos committed
272 273 274 275

    try:
        subject_template = 'ui/docreq_email_subject.txt'
        subject = render_to_string(subject_template,
276 277 278 279 280
                                   {
                                       'user': request.user,
                                       'url': url,
                                   },
        ).strip()
André Anjos's avatar
André Anjos committed
281 282
        body_template = 'ui/docreq_email_body.txt'
        body = render_to_string(body_template,
283 284 285 286 287 288
                                {
                                    'user': request.user,
                                    'url': url,
                                    'beat_version': __version__,
                                },
        )
André Anjos's avatar
André Anjos committed
289
        message = EmailMessage(subject=subject, body=body,
290
                               from_email=settings.DEFAULT_FROM_EMAIL, to=[to], reply_to=[cc])
André Anjos's avatar
André Anjos committed
291 292 293 294
        message.send()
    except Exception:
        import traceback
        logger.warn("Could not send e-mail to `%s' (cc: `%s') about " \
295 296
                    "documentation request for `%s'. Exception caught: %s", to, cc,
                    url, traceback.format_exc())
André Anjos's avatar
André Anjos committed
297 298 299 300

    return HttpResponse()


301 302 303
#----------------------------------------------------------


André Anjos's avatar
André Anjos committed
304 305 306 307 308 309 310 311 312 313 314 315 316 317
@login_required
def user_settings(request):
    '''User settings page (password and token change)'''

    user = request.user

    if request.method == 'POST':

        if 'password' in request.POST:
            password_change_form = \
                PasswordChangeForm(data=request.POST, user=user)
            if password_change_form.is_valid():
                password_change_form.save()
                messages.add_message(request, messages.SUCCESS,
318
                                     'Password changed successfully')
André Anjos's avatar
André Anjos committed
319 320 321 322 323 324 325

        elif 'token' in request.POST:

            user.auth_token.delete()
            Token.objects.create(user=user)
            password_change_form = PasswordChangeForm(user=user)
            messages.add_message(request, messages.SUCCESS,
326
                                 'Token changed successfully')
André Anjos's avatar
André Anjos committed
327 328 329 330 331

    else:

        password_change_form = PasswordChangeForm(user=user)

332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
    return render(request,
                  'ui/user_settings.html',
                  {
                      'password_change_form': password_change_form,
                      'token' : user.auth_token,
                      'statistics' : {
                          'nb_experiments': user.experiments.count(),
                          'nb_public_experiments': user.experiments.filter(sharing=Shareable.PUBLIC).count(),
                          'nb_attested_experiments': user.experiments.filter(~Q(attestation=None)).count(),
                          'nb_toolchains': user.toolchains.count(),
                          'nb_public_toolchains': user.toolchains.filter(sharing=Shareable.PUBLIC).count(),
                          'nb_algorithms': user.algorithms.count(),
                          'nb_public_algorithms': user.algorithms.filter(sharing=Shareable.PUBLIC).count(),
                          'nb_libraries': user.algorithms.count(),
                          'nb_public_libraries': user.librarys.filter(sharing=Shareable.PUBLIC).count(),
                          'nb_dataformats': user.dataformats.count(),
                          'nb_public_dataformats': user.dataformats.filter(sharing=Shareable.PUBLIC).count(),
                      },
                  })
351 352 353 354 355 356 357 358 359 360 361 362 363


#----------------------------------------------------------


def empty_error500_for_tests(request):
    '''Custom error 500 view used ONLY DURING THE TESTS

    Without it, Django tries to render the custom one from the website when an error
    occurs in some tests, but fails because some variables needed by the template aren't
    found.
    '''
    return HttpResponse()