views.py 23.1 KB
Newer Older
1
# -*- coding: utf-8 -*-
Dudás Ádám committed
2 3
from datetime import datetime
from django.conf import settings
Őry Máté committed
4
from datetime import timedelta as td
Dudás Ádám committed
5 6 7 8
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.contrib import messages
from django.core.exceptions import PermissionDenied
9
from django.core import signing, urlresolvers
Dudás Ádám committed
10 11 12 13 14 15 16 17 18 19 20 21
from django.core.mail import mail_managers, send_mail
from django.db import transaction
from django.forms import ModelForm, Textarea
from django.http import Http404
from django.shortcuts import render, render_to_response, get_object_or_404, redirect
from django.template import RequestContext
from django.template.loader import render_to_string
from django.utils.decorators import method_decorator
from django.utils.translation import get_language as lang
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.http import *
from django.views.generic import *
22
from firewall.tasks import *
23
from cloud.settings import store_settings
Dudás Ádám committed
24
from one.models import *
Dányi Bence committed
25
from school.models import *
Dudás Ádám committed
26
import django.contrib.auth as auth
27
import json
28
import logging
tarokkk committed
29
import subprocess
30 31

logger = logging.getLogger(__name__)
Dudás Ádám committed
32 33 34

def _list_instances(request):
    instances = Instance.objects.exclude(state='DONE').filter(owner=request.user)
35
    instances = instances.exclude(state='DONE')
Dudás Ádám committed
36 37
    return instances

38 39 40
def info(request):
    return render_to_response("info.html", RequestContext(request, {}))

41
def index(request):
42 43 44 45
    if request.user.is_authenticated():
        return redirect(home)
    else:
        return redirect(info)
46 47


Dudás Ádám committed
48 49 50
@require_GET
@login_required
def home(request):
51
    instances = _list_instances(request)
52 53 54 55
    shares = [s for s in request.user.person_set.all()[0].get_shares()]
    for i, s in enumerate(shares):
        s.running_shared = s.instance_set.all().exclude(state="DONE").filter(owner=request.user).count()
        shares[i] = s
56 57 58 59 60
    try:
        details = UserCloudDetails.objects.get(user=request.user)
    except UserCloudDetails.DoesNotExist:
        details = UserCloudDetails(user=request.user)
        details.save()
x committed
61 62 63 64
    try:
        generated_public_key = details.ssh_key.id
    except:
        generated_public_key = -1
65
    return render_to_response("home.html", RequestContext(request, {
66
        'instances': instances,
67
        'shares': shares,
68 69
        'templates': Template.objects.filter(state='READY'),
        'mytemplates': Template.objects.filter(owner=request.user),
Dányi Bence committed
70
        'groups': request.user.person_set.all()[0].owned_groups.all(),
71
        'semesters': Semester.objects.all(),
72
        'userdetails': details,
x committed
73
        'keys': request.user.sshkey_set.exclude(id=generated_public_key).all(),
74
        'storeserv': store_settings['store_public'],
Dudás Ádám committed
75 76
        }))

77 78 79 80 81 82 83
@login_required
def ajax_template_delete(request):
    try:
        template_id = request.POST['id']
    except:
        return HttpResponse(unicode(_("Invalid template ID.")), status=404)
    template = get_object_or_404(Template, id=template_id)
84
    if template.running_instances() > 0:
85 86 87
        return HttpResponse(unicode(_("There are running instances of this template.")), status=404)
    elif template.share_set.exists():
        return HttpResponse(unicode(_("Template is still shared.")), status=404)
88 89
    elif template.owner != request.user:
        return HttpResponse(unicode(_("You don't have permission to delete this template.")), status=404)
90
    else:
91 92 93 94
        if template.safe_delete():
            return HttpResponse(unicode(_("Template successfully deleted.")))
        else:
            return HttpResponse(unicode(_("Unexpected error happened.")), status=404)
95

96 97
def ajax_template_name_unique(request):
    name = request.GET['name']
98 99 100 101 102
    s = "True"
    if Template.objects.filter(name=name).exists():
        s = "False"
    return HttpResponse(s)

103 104
@login_required
def vm_credentials(request, iid):
105 106
    try:
        vm = get_object_or_404(Instance, pk=iid, owner=request.user)
107 108 109
        proto = len(request.META["REMOTE_ADDR"].split('.')) == 1
        vm.hostname = vm.get_connect_host(use_ipv6=proto)
        vm.port = vm.get_port(use_ipv6=proto)
110
        return render_to_response('vm-credentials.html', RequestContext(request, { 'i' : vm }))
111
    except:
112
        return HttpResponse(_("Could not get Virtual Machine credentials."), status=404)
113
        messages.error(request, _('Failed to power off virtual machine.'))
114

115 116
class AjaxTemplateWizard(View):
    def get(self, request, *args, **kwargs):
117 118 119
        return render_to_response('new-template-flow-1.html', RequestContext(request, {
            'templates': [t for t in Template.objects.filter(public=True).all()] +
                         [t for t in Template.objects.filter(owner=request.user).all()],
120 121 122 123 124
            }))
    def post(self, request, *args, **kwargs):
        base = get_object_or_404(Template, id=request.POST['base'])
        if base.owner != request.user and not base.public and not request.user.is_superuser:
            raise PermissionDenied()
125 126 127 128
        try:
            maxshare = Template.objects.order_by('-pk')[0].pk + 1
        except:
            maxshare = 1
129 130 131
        return render_to_response('new-template-flow.html', RequestContext(request, {
            'sizes': InstanceType.objects.all(),
            'base': base,
132
            'maxshare': maxshare,
133 134 135
            }))
ajax_template_wizard = login_required(AjaxTemplateWizard.as_view())

136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
class AjaxTemplateEditWizard(View):
    def get(self, request, id, *args, **kwargs):
        template = get_object_or_404(Template, id=id)
        if template.owner != request.user and not template.public and not request.user.is_superuser:
            raise PermissionDenied()
        return render_to_response('edit-template-flow.html', RequestContext(request, {
            'sizes': InstanceType.objects.all(),
            'template': template,
            }))
    def post(self, request, id, *args, **kwargs):
        template = get_object_or_404(Template, id=id)
        if template.owner != request.user and not template.public and not request.user.is_superuser:
            raise PermissionDenied()
        template.instance_type_id = request.POST['size']
        template.description = request.POST['description']
        template.name = request.POST['name']
        template.save()
        return redirect(home)

ajax_template_edit_wizard = login_required(AjaxTemplateEditWizard.as_view())

Őry Máté committed
157 158 159

class AjaxShareWizard(View):
    def get(self, request, id, gid=None, *args, **kwargs):
Őry Máté committed
160 161 162
        det = UserCloudDetails.objects.get(user=request.user)
        if det.get_weighted_share_count() >= det.share_quota:
            return HttpResponse(unicode(_('You do not have any free share quota.')))
Őry Máté committed
163 164 165 166 167 168 169 170 171
        types = TYPES_L
        types[0]['default'] = True
        for i, t in enumerate(types):
            t['deletex'] = datetime.now() + td(seconds=1) + t['delete'] if t['delete'] else None
            t['suspendx'] = datetime.now() + td(seconds=1) + t['suspend'] if t['suspend'] else None
            types[i] = t
        if gid:
            gid = get_object_or_404(Group, id=gid)

Őry Máté committed
172
        return render_to_response('new-share.html', RequestContext(request, {
Őry Máté committed
173 174 175 176 177 178
            'base': get_object_or_404(Template, id=id),
            'groups': request.user.person_set.all()[0].owned_groups.all(),
            'types': types,
            'group': gid,
            }))
    def post(self, request, id, gid=None, *args, **kwargs):
Őry Máté committed
179
        det = UserCloudDetails.objects.get(user=request.user)
Őry Máté committed
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
        base = get_object_or_404(Template, id=id)
        if base.owner != request.user and not base.public and not request.user.is_superuser:
            raise PermissionDenied()
        group = None
        if gid:
            group = get_object_or_404(Group, id=gid)
        else:
            group = get_object_or_404(Group, id=request.POST['group'])

        if not group.owners.filter(user=request.user).exists():
            raise PermissionDenied()
        stype = request.POST['type']
        if not stype in TYPES.keys():
            raise PermissionDenied()
        il = request.POST['instance_limit']
Őry Máté committed
195 196 197
        if det.get_weighted_share_count() + int(il)*base.instance_type.credit > det.share_quota:
            messages.error(request, _('You do not have enough free share quota.'))
            return redirect('/')
Őry Máté committed
198 199
        s = Share.objects.create(name=request.POST['name'], description=request.POST['description'],
                type=stype, instance_limit=il, per_user_limit=request.POST['per_user_limit'],
Őry Máté committed
200
                group=group, template=base, owner=request.user)
Őry Máté committed
201
        messages.success(request, _('Successfully shared %s.') % base)
Őry Máté committed
202
        return redirect(group)
Őry Máté committed
203 204
ajax_share_wizard = login_required(AjaxShareWizard.as_view())

205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
class AjaxShareEditWizard(View):
    def get(self, request, id, *args, **kwargs):
        det = UserCloudDetails.objects.get(user=request.user)
        if det.get_weighted_share_count() >= det.share_quota:
            return HttpResponse(unicode(_('You do not have any free share quota.')))
        types = TYPES_L
        for i, t in enumerate(types):
            t['deletex'] = datetime.now() + td(seconds=1) + t['delete'] if t['delete'] else None
            t['suspendx'] = datetime.now() + td(seconds=1) + t['suspend'] if t['suspend'] else None
            types[i] = t
        share = get_object_or_404(Share, id=id)
        return render_to_response('edit-share.html', RequestContext(request, {
            'share': share,
            'types': types,
            }))
    def post(self, request, id, *args, **kwargs):
        det = UserCloudDetails.objects.get(user=request.user)
        share = get_object_or_404(Share, id=id)
        if share.owner != request.user and not request.user.is_superuser:
            raise PermissionDenied()
        stype = request.POST['type']
        if not stype in TYPES.keys():
            raise PermissionDenied()
        il = request.POST['instance_limit']
        if det.get_weighted_share_count() + int(il)*share.template.instance_type.credit > det.share_quota:
            messages.error(request, _('You do not have enough free share quota.'))
            return redirect('/')
        share.name=request.POST['name']
        share.description=request.POST['description']
        share.type=stype
        share.instance_limit=il
        share.per_user_limit=request.POST['per_user_limit']
        share.owner=request.user
        share.save()
        messages.success(request, _('Successfully edited share %s.') % share)
        return redirect(share.group)
ajax_share_edit_wizard = login_required(AjaxShareEditWizard.as_view())
Őry Máté committed
242 243


244
@require_POST
Dányi Bence committed
245
@login_required
246 247 248 249 250 251 252 253
def vm_saveas(request, vmid):
    inst = get_object_or_404(Instance, pk=vmid)
    if inst.owner != request.user and not request.user.is_superuser:
        raise PermissionDenied()
    inst.save_as()
    messages.success(request, _("Template is being saved..."))
    return redirect(inst)

254
def vm_new_ajax(request, template):
255
    return vm_new(request, template, redir=False)
Dányi Bence committed
256

257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
def _redirect_or_201(path, redir):
    if redir:
        return redirect(path)
    else:
        response = HttpResponse("Created", status=201)
        response['Location'] = path
        return response

def _template_for_save(base, request):
    if base.owner != request.user and not base.public and not request.user.is_superuser:
        raise PermissionDenied()
    name = request.POST['name']
    t = Template.objects.create(name=name, disk=base.disk, instance_type_id=request.POST['size'], network=base.network, owner=request.user)
    t.access_type = base.access_type
    t.description = request.POST['description']
    t.system = base.system
    t.save()
    return t

def _check_quota(request, template, share):
    """
    Returns if the given request is permitted to run the new vm.
    """
    det = UserCloudDetails.objects.get(user=request.user)
    if det.get_weighted_instance_count() + template.instance_type.credit >= det.instance_quota:
        messages.error(request, _('You do not have any free quota. You can not launch this until you stop an other instance.'))
        return False
    if share:
        if share.get_running() + 1 > share.instance_limit:
            messages.error(request, _('The share does not have any free quota. You can not launch this until someone stops an instance.'))
            return False
        elif share.get_running_or_stopped(request.user) + 1 > share.per_user_limit:
            messages.error(request, _('You do not have any free quota for this share. You can not launch this until you stop an other instance.'))
            return False
        if not share.group.members.filter(user=request.user) and not share.group.owners.filter(user=request.user):
            messages.error(request, _('You are not a member of the share group.'))
            return False
    return True

Dudás Ádám committed
296 297
@require_POST
@login_required
298
def vm_new(request, template=None, share=None, redir=True):
299 300
    base = None
    extra = None
tarokkk committed
301 302 303 304 305
    if template:
        base = get_object_or_404(Template, pk=template)
    else:
        share = get_object_or_404(Share, pk=share)
        base = share.template
306

307
    go = True
308
    if "name" in request.POST:
309 310 311 312 313 314 315
        try:
            base = _template_for_save(base, request)
            extra = "<RECONTEXT>YES</RECONTEXT>"
        except:
            messages.error(request, _('Can not create template.'))
            go = False
    go = go and _check_quota(request, base, share)
316 317 318 319 320 321 322

    if not share and not base.public and base.owner != request.user:
        messages.error(request, _('You have no permission to try this instance without a share. Launch a new instance through a share.'))
        go = False
    type = share.type if share else 'LAB'
    TYPES[type]['suspend']
    time_of_suspend = TYPES[type]['suspend']+datetime.now()
tarokkk committed
323 324 325 326
    if TYPES[type]['delete']:
        time_of_delete = TYPES[type]['delete']+datetime.now()
    else:
        time_of_delete = None
327 328 329 330 331 332 333 334 335
    inst = None
    if go:
        try:
            inst = Instance.submit(base, request.user, extra=extra, share=share)
        except Exception as e:
            logger.error('Failed to create virtual machine.' + unicode(e))
            messages.error(request, _('Failed to create virtual machine.'))
            inst = None
        if inst:
336
            inst.waiting = True
337 338 339
            inst.time_of_suspend = time_of_suspend
            inst.time_of_delete = time_of_delete
            inst.save()
340 341
    elif extra and base:
        base.delete()
342
    return _redirect_or_201(inst.get_absolute_url() if inst else '/', redir)
Dudás Ádám committed
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357

class VmListView(ListView):
    context_object_name = 'instances'
    template_name = 'list.html'

    def get_queryset(self):
        self.profile = request.user
        return Instance.objects.filter(owner=self.profile)

vm_list = login_required(VmListView.as_view())

@require_safe
@login_required
def vm_show(request, iid):
    inst = get_object_or_404(Instance, id=iid, owner=request.user)
358 359 360 361
    try:
        ports = inst.firewall_host.list_ports()
    except:
        ports = None
362 363 364 365 366
    try:
        details = UserCloudDetails.objects.get(user=request.user)
    except UserCloudDetails.DoesNotExist:
        details = UserCloudDetails(user=request.user)
        details.save()
367 368 369
    proto = len(request.META["REMOTE_ADDR"].split('.')) == 1
    inst.hostname = inst.get_connect_host(use_ipv6=proto)
    inst.port = inst.get_port(use_ipv6=proto)
Dudás Ádám committed
370
    return render_to_response("show.html", RequestContext(request,{
371 372 373
        'uri': inst.get_connect_uri(),
        'state': inst.state,
        'name': inst.name,
tarokkk committed
374
        'id': int(iid),
Dudás Ádám committed
375 376
        'age': inst.get_age(),
        'instances': _list_instances(request),
Őry Máté committed
377
        'i': inst,
378
        'booting' : not inst.active_since,
379
        'ports': ports,
380
        'userdetails': details
Dudás Ádám committed
381 382
        }))

383 384 385 386
@require_safe
@login_required
def vm_ajax_instance_status(request, iid):
    inst = get_object_or_404(Instance, id=iid, owner=request.user)
387 388 389
    return HttpResponse(json.dumps({
        'booting': not inst.active_since,
        'state': inst.state,
390
        'waiting': inst.waiting,
391 392 393
        'template': {
            'state': inst.template.state
        }}))
394

395 396 397 398 399 400 401
@login_required
def vm_ajax_rename(request, iid):
    inst = get_object_or_404(Instance, id=iid, owner=request.user)
    inst.name = request.POST['name']
    inst.save()
    return HttpResponse(json.dumps({'name': inst.name}))

402 403 404 405 406 407 408 409 410 411 412 413 414
def boot_token(request, token):
    try:
        id = signing.loads(token, salt='activate')
    except:
        return HttpResponse("Invalid token.")
    inst = get_object_or_404(Instance, id=id)
    if inst.active_since:
        return HttpResponse("Already booted?")
    else:
        inst.active_since = datetime.now()
        inst.save()
        return HttpResponse("KTHXBYE")

Őry Máté committed
415 416 417 418
class VmPortAddView(View):
    def post(self, request, iid, *args, **kwargs):
        try:
            public = int(request.POST['public'])
Dányi Bence committed
419

Őry Máté committed
420
            if public >= 22000 and public < 24000:
421
                raise ValidationError(_("Port number is in a restricted domain (22000 to 24000)."))
Őry Máté committed
422
            inst = get_object_or_404(Instance, id=iid, owner=request.user)
423 424 425 426 427
            if inst.template.network.nat:
                private = private=int(request.POST['private'])
            else:
                private = 0
            inst.firewall_host.add_port(proto=request.POST['proto'], public=public, private=private)
428
            messages.success(request, _(u"Port %d successfully added.") % public)
Őry Máté committed
429
        except:
430
            messages.error(request, _(u"Adding port failed."))
Őry Máté committed
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
#            raise
        return redirect('/vm/show/%d/' % int(iid))

    def get(self, request, iid, *args, **kwargs):
        return redirect('/')

vm_port_add = login_required(VmPortAddView.as_view())

@require_safe
@login_required
@require_GET
def vm_port_del(request, iid, proto, public):
    inst = get_object_or_404(Instance, id=iid, owner=request.user)
    try:
        inst.firewall_host.del_port(proto=proto, public=public)
446
        messages.success(request, _(u"Port %s successfully removed.") % public)
Őry Máté committed
447
    except:
448
        messages.error(request, _(u"Removing port failed."))
Őry Máté committed
449 450
    return redirect('/vm/show/%d/' % int(iid))

Dudás Ádám committed
451 452 453
class VmDeleteView(View):
    def post(self, request, iid, *args, **kwargs):
        try:
454 455 456 457
            inst = get_object_or_404(Instance, id=iid, owner=request.user)
            if inst.template.state != 'READY' and inst.template.owner == request.user:
                inst.template.delete()
            inst.delete()
Dudás Ádám committed
458 459 460
            messages.success(request, _('Virtual machine is successfully deleted.'))
        except:
            messages.error(request, _('Failed to delete virtual machine.'))
461 462 463 464
        if request.is_ajax():
            return HttpResponse("")
        else:
            return redirect('/')
Dudás Ádám committed
465 466 467

    def get(self, request, iid, *args, **kwargs):
        i = get_object_or_404(Instance, id=iid, owner=request.user)
468
        return render_to_response("confirm_delete.html", RequestContext(request, {
Dudás Ádám committed
469 470 471 472
            'i': i}))

vm_delete = login_required(VmDeleteView.as_view())

473
@login_required
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
#@require_POST
def vm_unshare(request, id, *args, **kwargs):
    s = get_object_or_404(Share, id=id)
    g = s.group
    if not g.owners.filter(user=request.user).exists():
        raise PermissionDenied()
    try:
        if s.get_running_or_stopped() > 0:
            messages.error(request, _('There are machines running of this share.'))
        else:
            s.delete()
            messages.success(request, _('Share is successfully removed.'))
    except:
        messages.error(request, _('Failed to remove share.'))
    return redirect(g)

@login_required
491 492 493 494 495 496 497 498 499 500 501 502 503
@require_POST
def vm_stop(request, iid, *args, **kwargs):
    try:
        get_object_or_404(Instance, id=iid, owner=request.user).stop()
        messages.success(request, _('Virtual machine is successfully stopped.'))
    except:
        messages.error(request, _('Failed to stop virtual machine.'))
    return redirect('/')

@login_required
@require_POST
def vm_resume(request, iid, *args, **kwargs):
    try:
Őry Máté committed
504 505
        obj = get_object_or_404(Instance, id=iid, owner=request.user)
        obj.resume()
506 507 508
        messages.success(request, _('Virtual machine is successfully resumed.'))
    except:
        messages.error(request, _('Failed to resume virtual machine.'))
Őry Máté committed
509 510 511 512
    try:
        obj.renew()
    except:
        pass
513 514 515 516
    return redirect('/')

@login_required
@require_POST
Őry Máté committed
517 518
def vm_renew(request, which, iid, *args, **kwargs):
    try:
519
        get_object_or_404(Instance, id=iid, owner=request.user).renew()
Őry Máté committed
520 521 522 523 524 525 526
        messages.success(request, _('Virtual machine is successfully renewed.'))
    except:
        messages.error(request, _('Failed to renew virtual machine.'))
    return redirect('/')

@login_required
@require_POST
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
def vm_power_off(request, iid, *args, **kwargs):
    try:
        get_object_or_404(Instance, id=iid, owner=request.user).poweroff()
        messages.success(request, _('Virtual machine is successfully powered off.'))
    except:
        messages.error(request, _('Failed to power off virtual machine.'))
    return redirect('/')

@login_required
@require_POST
def vm_restart(request, iid, *args, **kwargs):
    try:
        get_object_or_404(Instance, id=iid, owner=request.user).restart()
        messages.success(request, _('Virtual machine is successfully restarted.'))
    except:
        messages.error(request, _('Failed to restart virtual machine.'))
    return redirect('/')
Dudás Ádám committed
544

Dányi Bence committed
545 546 547 548 549 550 551
@login_required
@require_POST
def key_add(request):
    try:
        key=SshKey()
        key.key=request.POST['key']
        key.user=request.user
Dányi Bence committed
552
        key.full_clean()
Dányi Bence committed
553
        key.save()
x committed
554
        _update_keys(request.user)
Dányi Bence committed
555
    except ValidationError as e:
556 557 558
        for m in e.messages:
            messages.error(request, m)

x committed
559
    except:
560 561
        messages.error(request, _('Failed to add public key.'))
    else:
Őry Máté committed
562
        messages.success(request, _('Public key successfully added.'))
Dányi Bence committed
563 564 565 566 567 568
    return redirect('/')

@login_required
@require_POST
def key_ajax_delete(request):
    try:
569
        key=get_object_or_404(SshKey, id=request.POST['id'], user=request.user)
Dányi Bence committed
570
        key.delete()
x committed
571
        _update_keys(request.user)
Dányi Bence committed
572 573
    except:
        messages.error(request, _('Failed to delete public key'))
Dányi Bence committed
574 575 576 577 578 579 580 581 582
    return HttpResponse('OK')

@login_required
@require_POST
def key_ajax_reset(request):
    try:
        det=UserCloudDetails.objects.get(user=request.user)
        det.reset_smb()
        det.reset_keys()
x committed
583
        _update_keys(request.user)
Dányi Bence committed
584 585 586
    except:
        messages.error(request, _('Failed to reset keys'))
    return HttpResponse('OK')
Dányi Bence committed
587

x committed
588 589 590 591 592 593 594 595 596
def _update_keys(user):
    details = user.cloud_details
    password = details.smb_password
    key_list = []
    for key in user.sshkey_set.all():
        key_list.append(key.key)
    user = user.username
    StoreApi.updateauthorizationinfo(user, password, key_list)

tarokkk committed
597 598 599 600 601 602 603 604 605 606 607 608
def stat(request):
    values = subprocess.check_output(['/opt/webadmin/cloud/miscellaneous/stat/stat_wrap.sh'])
  #  values = '''
  #  {"CPU": {"USED_CPU": 2, "ALLOC_CPU": 0,
  #  "FREE_CPU": 98}, "MEM": {"FREE_MEM": 1685432, "ALLOC_MEM":0,
  #  "USED_MEM": 366284}}'''
    stat_dict = json.loads(values)
    return  HttpResponse(render_to_response("stat.html", RequestContext(
                request, {
                    'STAT' : stat_dict,
                }
            )))
x committed
609

Bach Dániel committed
610
def sites(request, site):
Őry Máté committed
611
    if site in [ "legal", "policy", "help", "support", "changelog", ]:
Bach Dániel committed
612 613 614 615
        return render_to_response("sites/%s.html" % site, RequestContext(request, {}))
    else:
        return redirect(home)

Dudás Ádám committed
616
# vim: et sw=4 ai fenc=utf8 smarttab :