Merge pull request #263 from nottinghamtec/feature/forum-embed

Forum embed
This commit is contained in:
David Taylor
2016-10-11 18:56:02 +01:00
committed by GitHub
13 changed files with 443 additions and 29 deletions

View File

@@ -2,23 +2,37 @@ from django.contrib.auth import REDIRECT_FIELD_NAME
from django.shortcuts import render_to_response from django.shortcuts import render_to_response
from django.template import RequestContext from django.template import RequestContext
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
def user_passes_test_with_403(test_func, login_url=None): from RIGS import models
def user_passes_test_with_403(test_func, login_url=None, oembed_view=None):
""" """
Decorator for views that checks that the user passes the given test. Decorator for views that checks that the user passes the given test.
Anonymous users will be redirected to login_url, while users that fail Anonymous users will be redirected to login_url, while users that fail
the test will be given a 403 error. the test will be given a 403 error.
If embed_view is set, then a JS redirect will be used, and a application/json+oembed
meta tag set with the url of oembed_view
(oembed_view will be passed the kwargs from the main function)
""" """
if not login_url: if not login_url:
from django.conf import settings from django.conf import settings
login_url = settings.LOGIN_URL login_url = settings.LOGIN_URL
def _dec(view_func): def _dec(view_func):
def _checklogin(request, *args, **kwargs): def _checklogin(request, *args, **kwargs):
if test_func(request.user): if test_func(request.user):
return view_func(request, *args, **kwargs) return view_func(request, *args, **kwargs)
elif not request.user.is_authenticated(): elif not request.user.is_authenticated():
return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path())) if oembed_view is not None:
extra_context = {}
extra_context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], reverse(oembed_view, kwargs=kwargs))
extra_context['login_url'] = "{0}?{1}={2}".format(login_url, REDIRECT_FIELD_NAME, request.get_full_path())
resp = render_to_response('login_redirect.html', extra_context, context_instance=RequestContext(request))
return resp
else:
return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path()))
else: else:
resp = render_to_response('403.html', context_instance=RequestContext(request)) resp = render_to_response('403.html', context_instance=RequestContext(request))
resp.status_code = 403 resp.status_code = 403
@@ -28,14 +42,14 @@ def user_passes_test_with_403(test_func, login_url=None):
return _checklogin return _checklogin
return _dec return _dec
def permission_required_with_403(perm, login_url=None):
def permission_required_with_403(perm, login_url=None, oembed_view=None):
""" """
Decorator for views that checks whether a user has a particular permission Decorator for views that checks whether a user has a particular permission
enabled, redirecting to the log-in page or rendering a 403 as necessary. enabled, redirecting to the log-in page or rendering a 403 as necessary.
""" """
return user_passes_test_with_403(lambda u: u.has_perm(perm), login_url=login_url) return user_passes_test_with_403(lambda u: u.has_perm(perm), login_url=login_url, oembed_view=oembed_view)
from RIGS import models
def api_key_required(function): def api_key_required(function):
""" """
@@ -64,4 +78,4 @@ def api_key_required(function):
if user_object.api_key != key: if user_object.api_key != key:
return error_resp return error_resp
return function(request, *args, **kwargs) return function(request, *args, **kwargs)
return wrap return wrap

View File

@@ -9,11 +9,13 @@ from django.shortcuts import get_object_or_404
from django.template import RequestContext from django.template import RequestContext
from django.template.loader import get_template from django.template.loader import get_template
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import HttpResponse from django.http import HttpResponse
from django.db.models import Q from django.db.models import Q
from django.contrib import messages from django.contrib import messages
from z3c.rml import rml2pdf from z3c.rml import rml2pdf
from PyPDF2 import PdfFileMerger, PdfFileReader from PyPDF2 import PdfFileMerger, PdfFileReader
import simplejson
from RIGS import models, forms from RIGS import models, forms
import datetime import datetime
@@ -47,6 +49,28 @@ class EventDetail(generic.DetailView):
model = models.Event model = models.Event
class EventOembed(generic.View):
model = models.Event
def get(self, request, pk=None):
embed_url = reverse('event_embed', args=[pk])
full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url)
data = {
'html': '<iframe src="{0}" frameborder="0" width="100%" height="250"></iframe>'.format(full_url),
'version': '1.0',
'type': 'rich',
}
json = simplejson.JSONEncoderForHTML().encode(data)
return HttpResponse(json, content_type="application/json")
class EventEmbed(EventDetail):
template_name = 'RIGS/event_embed.html'
class EventCreate(generic.CreateView): class EventCreate(generic.CreateView):
model = models.Event model = models.Event
form_class = forms.EventForm form_class = forms.EventForm
@@ -59,7 +83,7 @@ class EventCreate(generic.CreateView):
form = context['form'] form = context['form']
if re.search('"-\d+"', form['items_json'].value()): if re.search('"-\d+"', form['items_json'].value()):
messages.info(self.request, "Your item changes have been saved. Please fix the errors and save the event.") messages.info(self.request, "Your item changes have been saved. Please fix the errors and save the event.")
# Get some other objects to include in the form. Used when there are errors but also nice and quick. # Get some other objects to include in the form. Used when there are errors but also nice and quick.
for field, model in form.related_models.iteritems(): for field, model in form.related_models.iteritems():

File diff suppressed because one or more lines are too long

View File

@@ -147,3 +147,45 @@ ins {
}; };
} }
} }
html.embedded{
min-height:100%;
display: table;
width: 100%;
body{
padding:0;
display: table-cell;
vertical-align: middle;
width:100%;
background:none;
}
.embed_container{
border:5px solid #e9e9e9;
padding:12px 0px;
min-height:100%;
width:100%;
}
.source{
background: url('/static/imgs/pyrigs-avatar.png') no-repeat;
background-size: 16px 16px;
padding-left: 20px;
color: #000;
}
h3{
margin-top:10px;
margin-bottom:5px;
}
p{
margin-bottom:2px;
font-size: 11px;
}
.event-mic-photo{
max-width: 3em;
}
}

View File

@@ -0,0 +1,106 @@
{% extends 'base_embed.html' %}
{% load static from staticfiles %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<a href="/">
<span class="source"> R<small>ig</small> I<small>nformation</small> G<small>athering</small> S<small>ystem</small></span>
</a>
</div>
<div class="col-sm-12">
<span class="pull-right">
{% if object.mic %}
<div class="text-center">
<img src="{{ object.mic.profile_picture }}" class="event-mic-photo img-rounded"/>
</div>
{% elif object.is_rig %}
<span class="glyphicon glyphicon-exclamation-sign"></span>
{% endif %}
</span>
<h3>
<a {% if perms.RIGS.view_event %}href="{% url 'event_detail' object.pk %}"{% endif %}>
{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %}
| {{ object.name }} </a>
{% if object.venue %}
<small>at {{ object.venue }}</small>
{% endif %}
<br/><small>
{{ object.start_date|date:"D d/m/Y" }}
{% if object.has_start_time %}
{{ object.start_time|date:"H:i" }}
{% endif %}
{% if object.end_date or object.has_end_time %}
&ndash;
{% endif %}
{% if object.end_date and object.end_date != object.start_date %}
{{ object.end_date|date:"D d/m/Y" }}
{% endif %}
{% if object.has_end_time %}
{{ object.end_time|date:"H:i" }}
{% endif %}
</small>
</h3>
<div class="row">
<div class="col-xs-6">
<p>
<strong>Status:</strong>
{{ object.get_status_display }}
</p>
<p>
{% if object.is_rig %}
<strong>Client:</strong> {{ object.person.name }}
{% if object.organisation %}
for {{ object.organisation.name }}
{% endif %}
{% if object.dry_hire %}(Dry Hire){% endif %}
{% else %}
<strong>Non-Rig</strong>
{% endif %}
</p>
<p>
<strong>MIC:</strong>
{% if object.mic %}
{{object.mic.name}}
{% else %}
None
{% endif %}
</p>
</div>
<div class="col-xs-6">
{% if object.meet_at %}
<p>
<strong>Crew meet:</strong>
{{ object.meet_at|date:"H:i" }} {{ object.meet_at|date:"(Y-m-d)" }}
</p>
{% endif %}
{% if object.access_at %}
<p>
<strong>Access at:</strong>
{{ object.access_at|date:"H:i" }} {{ object.access_at|date:"(Y-m-d)" }}
</p>
{% endif %}
<p>
<strong>Last updated:</strong>
{{ object.last_edited_at }} by "{{ object.last_edited_by.initials }}"
</p>
</div>
</div>
{% if object.description %}
<p>
<strong>Description: </strong>
{{ object.description|linebreaksbr }}
</p>
{% endif %}
</table>
</div>
</div>
{% endblock %}

View File

@@ -213,6 +213,88 @@ class TestInvoiceDelete(TestCase):
# Check this didn't work # Check this didn't work
self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk)) self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk))
class TestEmbeddedViews(TestCase):
@classmethod
def setUpTestData(cls):
cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True, is_active=True, is_staff=True)
cls.events = {
1: models.Event.objects.create(name="TE E1", start_date=date.today()),
2: models.Event.objects.create(name="TE E2", start_date=date.today())
}
cls.invoices = {
1: models.Invoice.objects.create(event=cls.events[1]),
2: models.Invoice.objects.create(event=cls.events[2])
}
cls.payments = {
1: models.Payment.objects.create(invoice=cls.invoices[1], date=date.today(), amount=12.34, method=models.Payment.CASH)
}
def setUp(self):
self.profile.set_password('testuser')
self.profile.save()
def testLoginRedirect(self):
request_url = reverse('event_embed', kwargs={'pk': 1})
expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url)
# Request the page and check it redirects
response = self.client.get(request_url, follow=True)
self.assertRedirects(response, expected_url, status_code=302, target_status_code=200)
# Now login
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
# And check that it no longer redirects
response = self.client.get(request_url, follow=True)
self.assertEqual(len(response.redirect_chain), 0)
def testLoginCookieWarning(self):
login_url = reverse('login_embed')
response = self.client.post(login_url, follow=True)
self.assertContains(response, "Cookies do not seem to be enabled")
def testXFrameHeaders(self):
event_url = reverse('event_embed', kwargs={'pk': 1})
login_url = reverse('login_embed')
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
response = self.client.get(event_url, follow=True)
with self.assertRaises(KeyError):
response._headers["X-Frame-Options"]
response = self.client.get(login_url, follow=True)
with self.assertRaises(KeyError):
response._headers["X-Frame-Options"]
def testOEmbed(self):
event_url = reverse('event_detail', kwargs={'pk': 1})
event_embed_url = reverse('event_embed', kwargs={'pk': 1})
oembed_url = reverse('event_oembed', kwargs={'pk': 1})
alt_oembed_url = reverse('event_oembed', kwargs={'pk': 999})
alt_event_embed_url = reverse('event_embed', kwargs={'pk': 999})
# Test the meta tag is in place
response = self.client.get(event_url, follow=True, HTTP_HOST='example.com')
self.assertContains(response, '<link rel="alternate" type="application/json+oembed"')
self.assertContains(response, oembed_url)
# Test that the JSON exists
response = self.client.get(oembed_url, follow=True, HTTP_HOST='example.com')
self.assertEqual(response.status_code, 200)
self.assertContains(response, event_embed_url)
# Should also work for non-existant events
response = self.client.get(alt_oembed_url, follow=True, HTTP_HOST='example.com')
self.assertEqual(response.status_code, 200)
self.assertContains(response, alt_event_embed_url)
class TestSampleDataGenerator(TestCase): class TestSampleDataGenerator(TestCase):
@override_settings(DEBUG=True) @override_settings(DEBUG=True)
def test_generate_sample_data(self): def test_generate_sample_data(self):

View File

@@ -1,7 +1,8 @@
from django.conf.urls import patterns, include, url from django.conf.urls import patterns, url
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from RIGS import models, views, rigboard, finance, ical, versioning, forms from RIGS import models, views, rigboard, finance, ical, versioning, forms
from django.views.generic import RedirectView from django.views.generic import RedirectView
from django.views.decorators.clickjacking import xframe_options_exempt
from PyRIGS.decorators import permission_required_with_403 from PyRIGS.decorators import permission_required_with_403
from PyRIGS.decorators import api_key_required from PyRIGS.decorators import api_key_required
@@ -14,7 +15,8 @@ urlpatterns = patterns('',
url(r'^closemodal/$', views.CloseModal.as_view(), name='closemodal'), url(r'^closemodal/$', views.CloseModal.as_view(), name='closemodal'),
url('^user/login/$', 'RIGS.views.login', name='login'), url('^user/login/$', 'RIGS.views.login', name='login'),
url(r'^user/password_reset/$', 'django.contrib.auth.views.password_reset', {'password_reset_form':forms.PasswordReset}), url('^user/login/embed/$', xframe_options_exempt(views.login_embed), name='login_embed'),
url(r'^user/password_reset/$', 'django.contrib.auth.views.password_reset', {'password_reset_form': forms.PasswordReset}),
# People # People
url(r'^people/$', permission_required_with_403('RIGS.view_person')(views.PersonList.as_view()), url(r'^people/$', permission_required_with_403('RIGS.view_person')(views.PersonList.as_view()),
@@ -71,7 +73,7 @@ urlpatterns = patterns('',
url(r'^rigboard/calendar/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'), url(r'^rigboard/calendar/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
url(r'^rigboard/calendar/(?P<view>(month|week|day))/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'), url(r'^rigboard/calendar/(?P<view>(month|week|day))/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
url(r'^rigboard/calendar/(?P<view>(month|week|day))/(?P<date>(\d{4}-\d{2}-\d{2}))/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'), url(r'^rigboard/calendar/(?P<view>(month|week|day))/(?P<date>(\d{4}-\d{2}-\d{2}))/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
url(r'^rigboard/archive/$', RedirectView.as_view(permanent=True,pattern_name='event_archive')), url(r'^rigboard/archive/$', RedirectView.as_view(permanent=True, pattern_name='event_archive')),
url(r'^rigboard/activity/$', url(r'^rigboard/activity/$',
permission_required_with_403('RIGS.view_event')(versioning.ActivityTable.as_view()), permission_required_with_403('RIGS.view_event')(versioning.ActivityTable.as_view()),
name='activity_table'), name='activity_table'),
@@ -80,8 +82,14 @@ urlpatterns = patterns('',
name='activity_feed'), name='activity_feed'),
url(r'^event/(?P<pk>\d+)/$', url(r'^event/(?P<pk>\d+)/$',
permission_required_with_403('RIGS.view_event')(rigboard.EventDetail.as_view()), permission_required_with_403('RIGS.view_event', oembed_view="event_oembed")(rigboard.EventDetail.as_view()),
name='event_detail'), name='event_detail'),
url(r'^event/(?P<pk>\d+)/embed/$',
xframe_options_exempt(login_required(login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())),
name='event_embed'),
url(r'^event/(?P<pk>\d+)/oembed_json/$',
rigboard.EventOembed.as_view(),
name='event_oembed'),
url(r'^event/(?P<pk>\d+)/print/$', url(r'^event/(?P<pk>\d+)/print/$',
permission_required_with_403('RIGS.view_event')(rigboard.EventPrint.as_view()), permission_required_with_403('RIGS.view_event')(rigboard.EventPrint.as_view()),
name='event_print'), name='event_print'),
@@ -101,7 +109,7 @@ urlpatterns = patterns('',
permission_required_with_403('RIGS.view_event')(versioning.VersionHistory.as_view()), permission_required_with_403('RIGS.view_event')(versioning.VersionHistory.as_view()),
name='event_history', kwargs={'model': models.Event}), name='event_history', kwargs={'model': models.Event}),
# Finance # Finance
url(r'^invoice/$', url(r'^invoice/$',
@@ -140,10 +148,10 @@ urlpatterns = patterns('',
# User editing # User editing
url(r'^user/$', login_required(views.ProfileDetail.as_view()), name='profile_detail'), url(r'^user/$', login_required(views.ProfileDetail.as_view()), name='profile_detail'),
url(r'^user/(?P<pk>\d+)/$', url(r'^user/(?P<pk>\d+)/$',
permission_required_with_403('RIGS.view_profile')(views.ProfileDetail.as_view()), permission_required_with_403('RIGS.view_profile')(views.ProfileDetail.as_view()),
name='profile_detail'), name='profile_detail'),
url(r'^user/edit/$', login_required(views.ProfileUpdateSelf.as_view()), url(r'^user/edit/$', login_required(views.ProfileUpdateSelf.as_view()),
name='profile_update_self'), name='profile_update_self'),
url(r'^user/reset_api_key$', login_required(views.ResetApiKey.as_view(permanent=False)), name='reset_api_key'), url(r'^user/reset_api_key$', login_required(views.ResetApiKey.as_view(permanent=False)), name='reset_api_key'),
# ICS Calendar - API key authentication # ICS Calendar - API key authentication
@@ -154,8 +162,7 @@ urlpatterns = patterns('',
url(r'^api/(?P<model>\w+)/(?P<pk>\d+)/$', login_required(views.SecureAPIRequest.as_view()), name="api_secure"), url(r'^api/(?P<model>\w+)/(?P<pk>\d+)/$', login_required(views.SecureAPIRequest.as_view()), name="api_secure"),
# Legacy URL's # Legacy URL's
url(r'^rig/show/(?P<pk>\d+)/$', RedirectView.as_view(permanent=True,pattern_name='event_detail')), url(r'^rig/show/(?P<pk>\d+)/$', RedirectView.as_view(permanent=True, pattern_name='event_detail')),
url(r'^bookings/$', RedirectView.as_view(permanent=True,pattern_name='rigboard')), url(r'^bookings/$', RedirectView.as_view(permanent=True, pattern_name='rigboard')),
url(r'^bookings/past/$', RedirectView.as_view(permanent=True,pattern_name='event_archive')), url(r'^bookings/past/$', RedirectView.as_view(permanent=True, pattern_name='event_archive')),
) )

View File

@@ -12,6 +12,8 @@ from django.contrib import messages
import datetime, pytz import datetime, pytz
import operator import operator
from registration.views import RegistrationView from registration.views import RegistrationView
from django.views.decorators.csrf import csrf_exempt
from RIGS import models, forms from RIGS import models, forms
@@ -29,12 +31,37 @@ class Index(generic.TemplateView):
def login(request, **kwargs): def login(request, **kwargs):
if request.user.is_authenticated(): if request.user.is_authenticated():
next = request.REQUEST.get('next', '/') next = request.REQUEST.get('next', '/')
return HttpResponseRedirect(request.REQUEST.get('next', '/')) return HttpResponseRedirect(next)
else: else:
from django.contrib.auth.views import login from django.contrib.auth.views import login
return login(request) return login(request)
# This view should be exempt from requiring CSRF token.
# Then we can check for it and show a nice error
# Don't worry, django.contrib.auth.views.login will
# check for it before logging the user in
@csrf_exempt
def login_embed(request, **kwargs):
print("Running LOGIN")
if request.user.is_authenticated():
next = request.REQUEST.get('next', '/')
return HttpResponseRedirect(next)
else:
from django.contrib.auth.views import login
if request.method == "POST":
csrf_cookie = request.COOKIES.get('csrftoken', None)
if csrf_cookie is None:
messages.warning(request, 'Cookies do not seem to be enabled. Try logging in using a new tab.')
request.method = 'GET' # Render the page without trying to login
return login(request, template_name="registration/login_embed.html")
""" """
Called from a modal window (e.g. when an item is submitted to an event/invoice). Called from a modal window (e.g. when an item is submitted to an event/invoice).
May optionally also include some javascript in a success message to cause a load of May optionally also include some javascript in a success message to cause a load of

49
templates/base_embed.html Normal file
View File

@@ -0,0 +1,49 @@
{% load static from staticfiles %}
{% load raven %}
<!DOCTYPE html>
<html
dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}"
xml:lang="{% firstof LANGUAGE_CODE 'en' %}"
lang="{% firstof LANGUAGE_CODE 'en' %}"
class="embedded">
<head>
<base target="_blank" />
<!-- Open all links in a new tab, not in the iframe -->
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400italic,700,300,400' rel='stylesheet'
type='text/css'>
<link rel="stylesheet" type="text/css" href="{% static "css/screen.css" %}">
<script src="https://code.jquery.com/jquery-1.8.3.min.js"
integrity="sha256-YcbK69I5IXQftf/mYD8WY0/KmEDCv1asggHpJk1trM8=" crossorigin="anonymous"></script>
<script src="https://cdn.ravenjs.com/1.3.0/jquery,native/raven.min.js"></script>
<script>Raven.config('{% sentry_public_dsn %}').install()</script>
</head>
<body>
{% include "analytics.html" %}
<div class="embed_container">
<div class="container-fluid">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.level_tag }} alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
{{ message }}
</div>
{% endfor %}
{% endif %}
{% block content %}
{% endblock %}
</div>
</div>
{% block js %}
{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,24 @@
{% extends 'base.html' %}
{% load staticfiles %}
{% block title %}Login Required{% endblock %}
{% block js %}
<script>
document.location = "{{login_url}}"
</script>
{% endblock %}
{% block extra-head %}
{% if oembed_url %}
<link rel="alternate" type="application/json+oembed"
href="{{oembed_url}}"
title="RIGS Embed" />
{% endif %}
{% endblock %}
{% block content %}
<div class="text-center">
<h2>Login is required for this page</h2>
<a href="{{login_url}}" class="btn btn-primary">Login</a>
</div>
{% endblock %}

View File

@@ -3,5 +3,8 @@
{% block title %}Login{% endblock %} {% block title %}Login{% endblock %}
{% block content %} {% block content %}
<div class="text-center">
<h1>R<small>ig</small> I<small>nformation</small> G<small>athering</small> S<small>ystem</small></h1>
</div>
{% include 'registration/loginform.html' %} {% include 'registration/loginform.html' %}
{% endblock %} {% endblock %}

View File

@@ -0,0 +1,34 @@
{% extends 'base_embed.html' %}
{% load widget_tweaks %}
{% block title %}Login{% endblock %}
{% block content %}
<div class="text-center">
<h1>R<small>ig</small> I<small>nformation</small> G<small>athering</small> S<small>ystem</small></h1>
</div>
{% include 'form_errors.html' %}
<div class="col-sm-6 col-sm-offset-3 col-lg-4 col-lg-offset-4">
<form id="loginForm" action="" method="post" role="form" target="_self">{% csrf_token %}
<div class="form-group">
<label for="id_username">{{ form.username.label }}</label>
{% render_field form.username class+="form-control" placeholder=form.username.label %}
</div>
<div class="form-group">
<label for="{{ form.password.id_for_label }}">{{ form.password.label }}</label>
{% render_field form.password class+="form-control" placeholder=form.password.label %}
</div>
<div class="text-right">
<input type="submit" value="Login" class="btn btn-primary"/>
</div>
<input type="hidden" name="next" value="{{ next }}"/>
</form>
</div>
{% endblock %}

View File

@@ -3,7 +3,7 @@
{% include 'form_errors.html' %} {% include 'form_errors.html' %}
<div class="col-sm-6 col-sm-offset-3 col-lg-4 col-lg-offset-4"> <div class="col-sm-6 col-sm-offset-3 col-lg-4 col-lg-offset-4">
<form action="{% url 'login' %}" method="post" role="form">{% csrf_token %} <form action="{% url 'login' %}" method="post" role="form" target="_self">{% csrf_token %}
<div class="form-group"> <div class="form-group">
<label for="id_username">{{ form.username.label }}</label> <label for="id_username">{{ form.username.label }}</label>
{% render_field form.username class+="form-control" placeholder=form.username.label autofocus="" %} {% render_field form.username class+="form-control" placeholder=form.username.label autofocus="" %}
@@ -12,9 +12,11 @@
<label for="{{ form.password.id_for_label }}">{{ form.password.label }}</label> <label for="{{ form.password.id_for_label }}">{{ form.password.label }}</label>
{% render_field form.password class+="form-control" placeholder=form.password.label %} {% render_field form.password class+="form-control" placeholder=form.password.label %}
</div> </div>
<a href="{% url 'registration_register' %}" class="btn">Register</a> <div class="text-right">
<a href="{% url 'password_reset' %}" class="btn">Forgotten Password</a> <a href="{% url 'registration_register' %}" class="btn">Register</a>
<input type="submit" value="Login" class="btn btn-primary"/> <a href="{% url 'password_reset' %}" class="btn">Forgotten Password</a>
<input type="hidden" name="next" value="{{ next }}"/> <input type="submit" value="Login" class="btn btn-primary"/>
<input type="hidden" name="next" value="{{ next }}"/>
</div>
</form> </form>
</div> </div>