changeset 209:34de8b88da36

Rework/clean-up 'my'
author Sylvain Beucler <beuc@beuc.net>
date Sat, 31 Jul 2010 13:22:48 +0200
parents cc3105877a0e
children 064c3d182f7f
files savane/my/forms.py savane/my/tests.py savane/my/urls.py savane/my/views.py savane/svmain/models.py savane/utils/__init__.py templates/my/index.html templates/my/ssh.html templates/my/ssh_gpg.html
diffstat 9 files changed, 220 insertions(+), 181 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/savane/my/forms.py
@@ -0,0 +1,69 @@
+# Manage user attributes
+# Copyright (C) 2009  Sylvain Beucler
+# Copyright (C) 2009  Jonathan Gonzalez V.
+#
+# This file is part of Savane.
+#
+# Savane is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Savane 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.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from django import forms
+from django.utils.translation import ugettext, ugettext_lazy as _
+from savane.utils import *
+
+class MailForm( forms.Form ):
+    email = forms.EmailField(required=True)
+    action = forms.CharField( widget=forms.HiddenInput, required=True, initial='update_mail' )
+
+class IdentityForm(forms.Form):
+    first_name = forms.CharField(required = True)
+    last_name = forms.CharField(required = False)
+    gpg_key = forms.CharField(widget=forms.Textarea(attrs={'cols':'70','rows':'15'}), required=False,
+                              help_text=_("You can write down here your (ASCII) public key (gpg --export --armor keyid)"))
+    action = forms.CharField(widget=forms.HiddenInput, required=True, initial='update_identity')
+
+class SSHForm(forms.Form):
+    key_file = forms.FileField(required=False, help_text=_("Be sure to upload the file ending with .pub"))
+    key = forms.CharField(widget=forms.TextInput(attrs={'size':'60'}), required=False)
+
+    def clean_key(self):
+        ssh_key = self.cleaned_data['key']
+
+        # String is not mandatory
+        if len(ssh_key) == 0:
+            return None
+
+        try:
+            ssh_key_fingerprint(ssh_key)
+        except Exception as e:
+            raise forms.ValidationError(_("The uploaded string is not a public key file: %s") % e)
+        return ssh_key
+
+    def clean_key_file(self):
+        ssh_key_file = self.cleaned_data['key_file']
+
+        # File is not mandatory
+        if ssh_key_file is None:
+            return None
+
+        # Avoid large file attacks
+        if ssh_key_file.size > 100*1024:
+            return None
+
+        ssh_key = ssh_key_file.read()
+        try:
+            ssh_key_fingerprint(ssh_key)
+        except Exception as e:
+            raise forms.ValidationError(_("The uploaded file is not a public key file: %s") % e)
+
+        return ssh_key_file
--- a/savane/my/tests.py
+++ b/savane/my/tests.py
@@ -21,6 +21,13 @@
 from django.core.urlresolvers import reverse
 import django.contrib.auth.models as auth_models
 import re
+import tempfile
+from savane.utils import ssh_key_fingerprint
+
+__test__ = {"doctest": """
+>>> ssh_key_fingerprint("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDbYV67YG54OX3/c7GNIG7zS1sSF3ddhCwhodEGpcbkQs4QOi7gcZHopyjBqvhyJB5fu76odOqI9KngW5IfpPX4lK/3kZZ8QISiF6nekB8wbi49hlB9K8j7NZ7rBTIsKApVNFqd4vriE9m7842soBOc6/sYSEemHxjA7+d+qbkV8j5wuo1QH0ynA5jPMI8RHhTtUBEZIJK2AFUB42bx2XFakhSh5K2DAfZyZ2dKeRkKRRbFzr0eAvbyCPKT93seWAFypETiomKbjMBRvMJyfpTcx4legzs9oGfeLHIb3V0oyM3ysXdqkwoOwO43qCcG/lDFvonzBGlDKh/T07kVXdLh")
+'2048 6e:57:73:c6:92:16:62:b8:cc:ed:01:3f:17:95:24:51 (RSA)\n'
+"""}
 
 class SimpleTest(TestCase):
     #fixtures = [
@@ -36,12 +43,32 @@
         auth_models.User.objects.create_user(username='test', email='test@test.tld', password='test')
         self.assertTrue(self.client.login(username='test', password='test'))
 
+        # Contact info
         response = self.client.get(reverse('savane:my:conf'))
         self.assertEqual(response.status_code, 200)
         response = self.client.post(reverse('savane:my:conf'),
-                                    {'action': 'update_identity', 'name': 'Lambda', 'last_name': 'Visitor'})
-        self.assertEqual(response.status_code, 200)
+                                    {'action': 'update_identity', 'first_name': 'Lambda', 'last_name': 'Visitor'})
+        self.assertEqual(response.status_code, 302)
 
         response = self.client.post(reverse('savane:my:conf'),
-                                    {'action': 'update_identity', 'name': '', 'last_name': 'Visitor'})
-        self.assertFormError(response, 'form_identity', 'name', u'This field is required.')
+                                    {'action': 'update_identity', 'first_name': '', 'last_name': 'Visitor'})
+        self.assertFormError(response, 'form_identity', 'first_name', u'This field is required.')
+
+        # SSH keys
+        # - string
+        response = self.client.get(reverse('savane:my:ssh'))
+        self.assertEqual(response.status_code, 200)
+        response = self.client.post(reverse('savane:my:ssh'),
+                                    {'key': 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDbYV67YG54OX3/c7GNIG7zS1sSF3ddhCwhodEGpcbkQs4QOi7gcZHopyjBqvhyJB5fu76odOqI9KngW5IfpPX4lK/3kZZ8QISiF6nekB8wbi49hlB9K8j7NZ7rBTIsKApVNFqd4vriE9m7842soBOc6/sYSEemHxjA7+d+qbkV8j5wuo1QH0ynA5jPMI8RHhTtUBEZIJK2AFUB42bx2XFakhSh5K2DAfZyZ2dKeRkKRRbFzr0eAvbyCPKT93seWAFypETiomKbjMBRvMJyfpTcx4legzs9oGfeLHIb3V0oyM3ysXdqkwoOwO43qCcG/lDFvonzBGlDKh/T07kVXdLh'})
+        self.assertEqual(response.status_code, 302)
+        response = self.client.post(reverse('savane:my:ssh'), {'key': 'ssh-rsa AAAABBM= me@myhost'})
+        self.assertFormError(response, 'form', 'key', u'The uploaded string is not a public key file: '
+                             + 'SSH error: is not a public key file.\n')
+
+        # - file upload
+        f = tempfile.TemporaryFile()
+        f.write('ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDbYV67YG54OX3/c7GNIG7zS1sSF3ddhCwhodEGpcbkQs4QOi7gcZHopyjBqvhyJB5fu76odOqI9KngW5IfpPX4lK/3kZZ8QISiF6nekB8wbi49hlB9K8j7NZ7rBTIsKApVNFqd4vriE9m7842soBOc6/sYSEemHxjA7+d+qbkV8j5wuo1QH0ynA5jPMI8RHhTtUBEZIJK2AFUB42bx2XFakhSh5K2DAfZyZ2dKeRkKRRbFzr0eAvbyCPKT93seWAFypETiomKbjMBRvMJyfpTcx4legzs9oGfeLHIb3V0oyM3ysXdqkwoOwO43qCcG/lDFvonzBGlDKh/T07kVXdLh')
+        f.flush()
+        f.seek(0)
+        response = self.client.post(reverse('savane:my:ssh'), {'key_file': f})
+        self.assertEqual(response.status_code, 302)
--- a/savane/my/urls.py
+++ b/savane/my/urls.py
@@ -26,39 +26,40 @@
 import django.contrib.auth.models as auth_models
 from savane.my.filters import *
 from savane.django_utils import decorated_patterns
+from django.utils.translation import ugettext, ugettext_lazy as _
 
 urlpatterns = patterns ('',)
 
 urlpatterns += decorated_patterns ('', login_required,
   url(r'^$', direct_to_template,
       { 'template' : 'my/index.html',
-        'extra_context' : { 'title' : 'My account', }, },
+        'extra_context' : { 'title' : _("My account configuration"), }, },
       name='index'),
   url('^conf/$', views.conf,
-      { 'extra_context' : {'title' : 'Contact info', }, },
+      { 'extra_context' : {'title' : _("Contact info"), }, },
       name='conf'),
   url('^conf/resume_skills/$', views.resume_skills,
-      { 'extra_context' : {'title' : 'Resume & skills', } },
+      { 'extra_context' : {'title' : _("Edit your resume & skills"), } },
       name='resume_skills'),
-  url('^conf/ssh_gpg/$', views.ssh_gpg,
-      { 'extra_context' : {'title' : 'SSH & GPG', } },
-      name='ssh_gpg'),
-  url('^conf/ssh_gpg/delete/$', views.ssh_delete,
+  url('^conf/ssh/$', views.ssh,
+      { 'extra_context' : {'title' : _("Change authorized keys"), } },
+      name='ssh'),
+  url('^conf/ssh/delete/$', views.ssh_delete,
       name='ssh_delete'),
   url('^i18n/$', direct_to_template,
       { 'template' : 'my/i18n.html',
-        'extra_context' : {'title' : 'Language', } },
+        'extra_context' : {'title' : _("Language"), } },
       name='i18n'),
   # TODO: set_lang only lasts for the user's session
   url('^i18n/', include('django.conf.urls.i18n')),
   url(r'^groups/$', only_mine(object_list),
       { 'queryset' : auth_models.Group.objects.all(),
-        'extra_context' : { 'title' : "My groups", },
+        'extra_context' : { 'title' : _("My groups"), },
         'template_name' : 'svmain/group_list.html', },
       name='group_list'),
   url(r'^memberships/$', only_mine(object_list),
       { 'queryset' : svmain_models.Membership.objects.all(),
-        'extra_context' : { 'title' : "My memberships", },
+        'extra_context' : { 'title' : _("My memberships"), },
         },
       name='membership_list'),
 )
--- a/savane/my/views.py
+++ b/savane/my/views.py
@@ -22,17 +22,16 @@
 from django.http import HttpResponseRedirect
 from django.contrib.auth import authenticate, login, logout
 from django.contrib.auth.decorators import login_required
-from django import forms
 from django.contrib import messages
-from django.utils.translation import ugettext as _
+from django.utils.translation import ugettext as _, ungettext
 from savane.svmain.models import SvUserInfo, SshKey
-from savane.utils import *
+from savane.my.forms import *
 from annoying.decorators import render_to
 
 @login_required()
 def conf(request, extra_context={}):
     form_mail = MailForm(initial={'email' : request.user.email})
-    form_identity = IdentityForm(initial={'name' : request.user.first_name,
+    form_identity = IdentityForm(initial={'first_name' : request.user.first_name,
                                           'last_name' : request.user.last_name})
     form = None
 
@@ -50,12 +49,14 @@
                 new_email = request.POST['email']
                 request.user.email = new_email
                 request.user.save()
-                messages.success(request, u"The E-Mail address was succesfully updated. New E-Mail address is <%s>" % new_email)
+                messages.success(request, _("The e-mail address was successfully updated. New e-mail address is <%s>") % new_email)
+                return HttpResponseRedirect("")  # reload
             elif action == 'update_identity':
-                request.user.first_name = request.POST['name']
+                request.user.first_name = request.POST['first_name']
                 request.user.last_name = request.POST['last_name']
                 request.user.save()
-                messages.success(request, u"Personal information changed.")
+                messages.success(request, _("Personal information changed."))
+                return HttpResponseRedirect("")  # reload
 
     context = { 'form_mail' : form_mail,
                 'form_identity' : form_identity,
@@ -72,73 +73,62 @@
                               context_instance=RequestContext(request))
 
 @login_required()
-def ssh_gpg(request, extra_context={}):
+def ssh(request, extra_context={}):
     info = request.user.svuserinfo
 
     error_msg = None
     success_msg = None
 
-    form_ssh = SSHForm()
-    form_gpg = GPGForm(initial={'gpg_key' : info.gpg_key})
+    form = SSHForm()
     ssh_keys = None
 
     if request.method == 'POST':
         form = None
-        action = request.POST['action']
-        if action == 'add_ssh':
-            form_ssh = SSHForm(request.POST, request.FILES)
-            form = form_ssh
-        elif action == 'update_gpg':
-            form_gpg = GPGForm(request.POST)
-            form = form_gpg
+        form = SSHForm(request.POST, request.FILES)
 
         if form is not None and form.is_valid():
-            if action == 'add_ssh':
-                if 'key' in request.POST:
-                    key = request.POST['key'].strip()
-                    if len(key) > 0:
-                        ssh_key = SshKey(ssh_key=key)
-                        request.user.sshkey_set.add(ssh_key)
-                        success_msg = 'Authorized keys stored.'
+            keys_saved = 0
 
-                if 'key_file' in request.FILES:
-                    ssh_key_file = request.FILES['key_file']
-                    if ssh_key_file is not None:
-                        key = ''
-                        for chunk in ssh_key_file.chunks():
-                            key = key + chunk
+            if 'key' in request.POST:
+                key = request.POST['key'].strip()
+                if len(key) > 0:
+                    ssh_key = SshKey(ssh_key=key)
+                    request.user.sshkey_set.add(ssh_key)
+                    keys_saved += 1
 
-                            if len(key) > 0:
-                                ssh_key = SshKey(ssh_key=key)
-                                request.user.sshkey_set.add(ssh_key)
-                                success_msg = 'Authorized keys stored.'
-
-                form_ssh = SSHForm()
+            if 'key_file' in request.FILES:
+                ssh_key_file = request.FILES['key_file']
+                if ssh_key_file is not None:
+                    key = ''
+                    for chunk in ssh_key_file.chunks():
+                        key = key + chunk
+                        if len(key) > 0:
+                            ssh_key = SshKey(ssh_key=key)
+                            request.user.sshkey_set.add(ssh_key)
+                            keys_saved += 1
 
-                if len( success_msg ) == 0:
-                    error_msg = 'Cannot added the public key'
-
-            elif action == 'update_gpg':
-                if 'gpg_key' in request.POST:
-                    info.gpg_key = request.POST['gpg_key']
-                    info.save()
-                    messages.success(request, _("GPG Key updated."))
+            if keys_saved > 0:
+                messages.success(request, ungettext('Key registered', '%(count)d keys registered', keys_saved) % {
+                        'count': keys_saved})
+                return HttpResponseRedirect("")  # reload
+            else:
+                error_msg = _("Error while registering keys")
+    else:
+        form_ssh = SSHForm()
 
     keys = request.user.sshkey_set.all()
     if keys is not None:
         ssh_keys = dict()
         for key in keys:
-            ssh_keys[key.pk] =  ssh_key_fingerprint( key.ssh_key )
-
+            ssh_keys[key.pk] = ssh_key_fingerprint(key.ssh_key)
 
-    context = { 'form_gpg' : form_gpg,
-                'form_ssh' : form_ssh,
+    context = { 'form' : form,
                 'ssh_keys' : ssh_keys,
                 'error_msg' : error_msg,
                 'success_msg' : success_msg,
                 }
     context.update(extra_context)
-    return render_to_response('my/ssh_gpg.html',
+    return render_to_response('my/ssh.html',
                               context,
                               context_instance=RequestContext(request))
 
@@ -150,54 +140,7 @@
             ssh_key = request.user.sshkey_set.get(pk=request.POST.get('key_pk', 0))
             ssh_key.delete()
         except SshKey.DoesNotExist:
-            messages.error(request, u"Cannot remove the selected key")
+            messages.error(request, _("Cannot remove the selected key"))
         return HttpResponseRedirect("../")
     else:
         return {}
-
-
-class MailForm( forms.Form ):
-    email = forms.EmailField(required=True)
-    action = forms.CharField( widget=forms.HiddenInput, required=True, initial='update_mail' )
-
-class IdentityForm( forms.Form ):
-    name = forms.CharField( required = True )
-    last_name = forms.CharField( required = False )
-    action = forms.CharField( widget=forms.HiddenInput, required=True, initial='update_identity' )
-
-class GPGForm( forms.Form ):
-    gpg_key = forms.CharField( widget=forms.Textarea( attrs={'cols':'70','rows':'15'} ), required=False )
-    action = forms.CharField( widget=forms.HiddenInput, required=True, initial='update_gpg' )
-
-class SSHForm( forms.Form ):
-    key_file = forms.FileField(required=False, help_text="Be sure to upload the file ending with .pub")
-    key = forms.CharField(widget=forms.TextInput(attrs={'size':'60'}), required=False)
-
-    action = forms.CharField(widget=forms.HiddenInput, required=True, initial='add_ssh')
-
-    def clean_key( self ):
-        ssh_key = self.cleaned_data['key']
-
-        try:
-            ssh_key_fingerprint(ssh_key)
-        except:
-            raise forms.ValidationError("The uploaded string is not a public key file")
-
-        return ssh_key
-
-    def clean_key_file( self ):
-        ssh_key_file = self.cleaned_data['key_file']
-
-        if ssh_key_file is None:
-            return ssh_key_file
-
-        ssh_key = str()
-        for chunk in ssh_key_file.chunks():
-            ssh_key = ssh_key + chunk
-
-        try:
-            ssh_key_fingerprint(ssh_key)
-        except:
-            raise forms.ValidationError("The uploaded file is not a public key file")
-
-        return ssh_key_file
--- a/savane/svmain/models.py
+++ b/savane/svmain/models.py
@@ -127,7 +127,8 @@
     #confirm_hash = models.CharField(max_length=96, blank=True, null=True)
 
     # Keys
-    gpg_key = models.TextField(blank=True)
+    gpg_key = models.TextField(blank=True,
+                               help_text="You can write down here your (ASCII) public key (gpg --export --armor keyid)")
     gpg_key_count = models.IntegerField(null=True, blank=True)
     # SSH keys: cf. SshKey above
 
--- a/savane/utils/__init__.py
+++ b/savane/utils/__init__.py
@@ -16,32 +16,31 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-import os
+import os, tempfile
 import random
 import time
 import re
-from subprocess import Popen, PIPE
-
-def ssh_key_fingerprint( ssh_key ):
-    if ssh_key is None or len(ssh_key) == 0:
-        return None
+import subprocess
+from django.utils.translation import ugettext as _
 
-    file_name = '/tmp/%d' % random.randint(0, int(time.time()))
-
-    tmp_file = open( file_name, 'wb+' )
-    tmp_file.write( ssh_key )
-    tmp_file.close()
+def ssh_key_fingerprint(ssh_key):
+    """
+    Check if the public key is valid using command-line 'ssh-keygen'
+    """
+    tmp_file = tempfile.NamedTemporaryFile()  # auto-removed
+    tmp_file.write(ssh_key)
+    tmp_file.flush()
 
-    cmd = 'ssh-keygen -l -f %s' % file_name
-    pipe = Popen( cmd, shell=True, stdout=PIPE).stdout
-    ssh_fp = pipe.readline()
-
-    cmd = 'rm %s' % file_name
-    piep = Popen( cmd, shell=True, stdout=PIPE)
+    args = ['ssh-keygen', '-l', '-f', tmp_file.name]
+    try:
+        process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    except OSError as e:
+        # Error running ssh-keygen, probably not installed
+        return 'Failed to run ssh-keygen, contact the site administrator'
+    out_err = process.communicate()
+    out = out_err[0]
 
-    res = re.search("not a public key file", ssh_fp)
-    if res is not None:
-        raise 'Not a public key'
+    if process.returncode != 0:
+        raise Exception(_("SSH error: %s") % out.replace(tmp_file.name+' ', ''))
 
-    return ssh_fp
-
+    return out.replace(tmp_file.name+' ', '')
--- a/templates/my/index.html
+++ b/templates/my/index.html
@@ -1,25 +1,26 @@
 {% extends "base.html" %}
+{% load i18n %}
 
 {% block content %}
 <div style="float: left; margin: 10px;">
-  <p>Configure</p>
+  <p>{% trans "Account configuration" %}</p>
 
   <p>
-    <a href="{% url savane:my:conf %}">Contact info</a><br />
-    <a href="{% url savane:my:ssh_gpg %}">SSH &amp; GPG</a><br />
-    <a href="{% url savane:my:resume_skills %}">My resume &amp; skills</a><br />
-    <a href="{% url savane:my:group_list %}">My groups</a><br />
+    <a href="{% url savane:my:conf %}">{% trans "Contact info" %}</a><br />
+    <a href="{% url savane:my:ssh %}">{% trans "SSH public keys" %}</a><br />
+    <a href="{% url savane:my:resume_skills %}">{% trans "Edit resume and skills" %}</a><br />
+    <a href="{% url savane:my:group_list %}">{% trans "My groups" %}</a><br />
     <a href="{% url savane:my:membership_list %}">My memberships</a><br />
-    <a href="{% url django.contrib.auth.views.password_change %}">Change my password</a><br />
-    <a href="{% url savane:my:i18n %}">Set language</a> ({{LANGUAGE_CODE}})<br />
+    <a href="{% url django.contrib.auth.views.password_change %}">{% trans "Change password" %}</a><br />
+    <a href="{% url savane:my:i18n %}">{% trans "Set language" %}</a> ({{LANGUAGE_CODE}})<br />
   </p>
 </div>
 
 <div style="float: left; margin: 10px;">
-  <p>Public information</p>
+  <p>{% trans "Public information" %}</p>
 
   <p>
-    <a href="{% url savane:svmain:user_detail request.user.username %}">My public page</a><br />
+    <a href="{% url savane:svmain:user_detail request.user.username %}">{% trans "View your public profile" %}</a><br />
   </p>
 </div>
 
new file mode 100644
--- /dev/null
+++ b/templates/my/ssh.html
@@ -0,0 +1,36 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block content %}
+<h2>Working SSH Keys</h2>
+
+{% if ssh_keys.items %}
+<p><code>$ ssh-keygen -l -f key.pub</code></p>
+{% endif %}
+{% for key_pk,key in ssh_keys.items %}
+<ul>
+<li>
+  {{ key }}
+  <form action="{% url savane:my:ssh_delete %}" method="post">{% csrf_token %}
+    <input type="hidden" name="key_pk" value="{{key_pk}}" /><input type="submit" value="Delete"</input>
+  </form>
+</li>
+</ul>
+{% empty %}
+  No SSH keys yet.
+{% endfor %}
+
+<h2>SSH Keys</h2>
+<form action="" method="post" enctype="multipart/form-data">{% csrf_token %}
+  {{ form.as_p }}
+  <br /><input type="submit" value="Add" name="Add" />
+</form>
+
+{% endblock %}
+{% comment %}
+Local Variables: **
+mode: django-html **
+tab-width: 4 **
+indent-tabs-mode: nil **
+End: **
+{% endcomment %}
deleted file mode 100644
--- a/templates/my/ssh_gpg.html
+++ /dev/null
@@ -1,38 +0,0 @@
-{% extends "base.html" %}
-{% load i18n %}
-
-{% block content %}
-<h2>Working SSH Keys</h2>
-{% for key_pk,key in ssh_keys.items %}
-<li>
-  {{ key }}
-  <form action="delete/" method="post">{% csrf_token %}
-    <input type="hidden" name="key_pk" value="{{key_pk}}" /><input type="submit" value="Delete"</input>
-  </form>
-</li>
-{% empty %}
-  No SSH keys yet.
-{% endfor %}
-
-<h2>SSH Keys</h2>
-<form action="" method="post" enctype="multipart/form-data">{% csrf_token %}
-  {{ form_ssh.as_p }}
-  <br /><input type="submit" value="Add" name="Add" />
-</form>
-
-<h2>GPG Key</h2>
-{% trans "You can write down here your (ASCII) public key (gpg --export --armor keyid):" %}
-<form action="" method="post">{% csrf_token %}
-  {{ form_gpg.as_p }}
-  <br /><input type="submit" value="Update" name="Update" />
-</form>
-
-
-{% endblock %}
-{% comment %}
-Local Variables: **
-mode: django-html **
-tab-width: 4 **
-indent-tabs-mode: nil **
-End: **
-{% endcomment %}