Compare commits

..

7 Commits

Author SHA1 Message Date
Tom Price
a3cdef8570 Tidy up the querying and move into model
This should really be in the model not in the view
2017-05-08 23:49:33 +01:00
Tom Price
7ec1c726a6 Optimise down to just the one SQL query for waiting invoices.
Cuts down on all the unnecessary queries and selects everything in one go.
Down from ~6 queries per invoice
2017-03-01 02:43:53 +00:00
Tom Price
4773b43081 Refactor outstanding invoice query to use django annotations.
Considerably improves performance and also removes the explict PSQL code.

Can be further improved by reducing the number of hits to the db in the template.
2017-03-01 02:05:06 +00:00
Tom Price
9694d407ae Merge pull request #275 from nottinghamtec/hotfix/home-links
Make forum go to actual forum
2017-02-06 15:47:12 +00:00
Sam Osborne
82aa2785ea Make forum go to actual forum
Currently goes to old forum :(
2017-02-06 15:35:29 +00:00
David Taylor
337dbd74fd Merge pull request #273 from nottinghamtec/hotfix/restore-pagination
Restore pagination to invoice waiting (requested by Emma)
2016-11-04 13:36:21 +00:00
David Taylor
caa55fe89a Restore pagination to invoice waiting (requested by Emma) 2016-11-04 13:31:06 +00:00
20 changed files with 150 additions and 548 deletions

View File

@@ -58,7 +58,6 @@ INSTALLED_APPS = (
'captcha', 'captcha',
'widget_tweaks', 'widget_tweaks',
'raven.contrib.django.raven_compat', 'raven.contrib.django.raven_compat',
'social.apps.django_app.default',
) )
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (
@@ -74,31 +73,6 @@ MIDDLEWARE_CLASSES = (
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
) )
AUTHENTICATION_BACKENDS = (
'RIGS.discourse.discourse.DiscourseAuth',
'django.contrib.auth.backends.ModelBackend',
)
SOCIAL_AUTH_PIPELINE = (
'social.pipeline.social_auth.social_details', # Load remote details
'social.pipeline.social_auth.social_uid', # Load remote ID
'social.pipeline.social_auth.auth_allowed', # Check not blacklisted
'social.pipeline.social_auth.social_user', # If already associated, login
'RIGS.discourse.pipeline.new_connection', # Choose a user account, much UI
'social.pipeline.social_auth.associate_user', # Associate the social auth with the user
'social.pipeline.social_auth.load_extra_data', # Save all the social info we have on this user
'RIGS.discourse.pipeline.update_avatar', # Load the avatar URL from the API, and save to user model
'social.pipeline.user.user_details', # Save any details that changed
)
DISCOURSE_HOST = os.environ.get('DISCOURSE_HOST') if os.environ.get('DISCOURSE_HOST') else 'http://localhost:4000'
DISCOURSE_SSO_SECRET = os.environ.get('DISCOURSE_SSO_SECRET') if os.environ.get('DISCOURSE_SSO_SECRET') else 'ABCDEFGHIJKLMNOP'
DISCOURSE_API_KEY = os.environ.get('DISCOURSE_API_KEY') if os.environ.get('DISCOURSE_HOST') else None
DISCOURSE_API_USER = os.environ.get('DISCOURSE_API_USER') if os.environ.get('DISCOURSE_HOST') else 'system'
REGISTRATION_OPEN = False # Disable built-in django registration - must register using forum
ROOT_URLCONF = 'PyRIGS.urls' ROOT_URLCONF = 'PyRIGS.urls'
WSGI_APPLICATION = 'PyRIGS.wsgi.application' WSGI_APPLICATION = 'PyRIGS.wsgi.application'
@@ -228,8 +202,6 @@ TEMPLATE_CONTEXT_PROCESSORS = (
"django.core.context_processors.tz", "django.core.context_processors.tz",
"django.core.context_processors.request", "django.core.context_processors.request",
"django.contrib.messages.context_processors.messages", "django.contrib.messages.context_processors.messages",
'social.apps.django_app.context_processors.backends',
'social.apps.django_app.context_processors.login_redirect',
) )

View File

@@ -18,7 +18,6 @@ urlpatterns = patterns('',
url('^user/', include('registration.backends.default.urls')), url('^user/', include('registration.backends.default.urls')),
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),
url('', include('social.apps.django_app.urls', namespace='social'))
) )
if settings.DEBUG: if settings.DEBUG:

View File

@@ -1,91 +0,0 @@
from __future__ import unicode_literals
from social.backends.base import BaseAuth
from django.conf import settings
from .sso import DiscourseSSO
class DiscourseAssociation(object):
""" Use Association model to save the nonce by force. """
def __init__(self, handle, secret='', issued=0, lifetime=0, assoc_type=''):
self.handle = handle # as nonce
self.secret = secret.encode() # not use
self.issued = issued # not use
self.lifetime = lifetime # not use
self.assoc_type = assoc_type # as state
class DiscourseAuth(BaseAuth):
"""Discourse authentication backend"""
name = 'discourse'
secret = settings.DISCOURSE_SSO_SECRET
host = settings.DISCOURSE_HOST
EXTRA_DATA = [
('username', 'username'),
('email', 'email'),
('external_id', 'external_id')
]
sso = DiscourseSSO(secret)
def get_and_store_nonce(self, url):
# Create a nonce
nonce = self.strategy.random_string(64)
# Store the nonce
association = DiscourseAssociation(nonce)
self.strategy.storage.association.store(url, association)
return nonce
def get_nonce(self, nonce):
try:
return self.strategy.storage.association.get(
server_url=self.host,
handle=nonce
)[0]
except IndexError:
pass
def remove_nonce(self, nonce_id):
self.strategy.storage.association.remove([nonce_id])
def get_user_id(self, details, response):
"""Return current user id."""
return int(response['external_id'])
def get_user_details(self, response):
"""Return user basic information (id and email only)."""
return {'username': response['username'],
'email': response['email'],
'fullname': response['name'].replace('+', ' ') if 'name' in response else '',
'first_name': '',
'last_name': ''}
def auth_url(self):
"""Build and return complete URL."""
nonce = self.get_and_store_nonce(self.host)
return self.host + self.sso.build_login_URL(nonce, self.redirect_uri)
def auth_complete(self, *args, **kwargs):
"""Completes login process, must return user instance."""
try:
if not self.sso.validate(self.data['sso'], self.data['sig']):
raise Exception("Someone wants to hack us!")
except KeyError:
raise Exception("SSO Error, please try again")
nonce = self.sso.get_nonce(self.data['sso'])
nonce_obj = self.get_nonce(nonce)
if nonce_obj:
self.remove_nonce(nonce_obj.id)
else:
raise Exception("Nonce does not match!")
kwargs.update({'response': self.sso.get_data(
self.data['sso']), 'backend': self})
return self.strategy.authenticate(*args, **kwargs)

View File

@@ -1,88 +0,0 @@
from django.core.urlresolvers import reverse
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.shortcuts import render_to_response
from django.core.exceptions import ValidationError
from django.conf import settings
import json
import requests
from social.pipeline.partial import partial
from RIGS.models import Profile
from RIGS import forms
class SocialRegisterForm(forms.ProfileRegistrationFormUniqueEmail):
def __init__(self, *args, **kwargs):
super(SocialRegisterForm, self).__init__(*args, **kwargs)
self.fields.pop('password1')
self.fields.pop('password2')
self.fields.pop('captcha')
self.fields['email'].widget.attrs['readonly'] = True
def clean_email(self):
initial = getattr(self, 'initial', None)
if(initial['email'] != self.cleaned_data['email']):
raise ValidationError("You cannot change the email")
return initial['email']
@partial
def new_connection(backend, details, response, user=None, is_new=False, social=None, request=None, *args, **kwargs):
if social is not None:
return
data = backend.strategy.request_data()
if data.get('UseCurrentAccount') is not None:
return
alreadyLoggedIn = user is not None
context = {
'details': details,
'alreadyLoggedIn': alreadyLoggedIn,
'loggedInUser': user,
}
if not alreadyLoggedIn:
completeUrl = reverse('social:complete', kwargs={'backend': backend.name})
context['login_url'] = "{0}?{1}={2}".format(reverse('login'), REDIRECT_FIELD_NAME, completeUrl)
if data.get('username') is None:
form = SocialRegisterForm(initial=details)
else:
form = SocialRegisterForm(data, initial=details)
if form.is_valid():
new_user = Profile.objects.create_user(**form.cleaned_data)
return {'user': new_user}
context['form'] = form
return render_to_response('RIGS/social-associate.html', context)
def update_avatar(backend, details, response, user=None, social=None, *args, **kwargs):
host = settings.DISCOURSE_HOST
api_key = settings.DISCOURSE_API_KEY
api_user = settings.DISCOURSE_API_USER
if social is not None:
url = "{}/users/{}.json".format(host, details['username'])
params = {
'api_key': api_key,
'api_username': api_user
}
resp = requests.get(url=url, params=params)
extraData = json.loads(resp.text)
avatar_template = extraData['user']['avatar_template']
if avatar_template and user.avatar_template != avatar_template:
user.avatar_template = avatar_template
user.save()
return

View File

@@ -1,46 +0,0 @@
import urllib
from hashlib import sha256
import hmac
from base64 import b64decode, b64encode
class DiscourseSSO:
def __init__(self, secret_key):
self.__secret_key = secret_key
def validate(self, payload, sig):
payload = urllib.unquote(payload)
computed_sig = hmac.new(
self.__secret_key.encode(),
payload.encode(),
sha256
).hexdigest()
return hmac.compare_digest(unicode(computed_sig), sig)
def get_nonce(self, payload):
payload = b64decode(urllib.unquote(payload)).decode()
d = dict(nonce.split("=") for nonce in payload.split('&'))
if 'nonce' in d and d['nonce'] != '':
return d['nonce']
else:
raise Exception("Nonce could not be found in payload")
def get_data(self, payload):
payload = urllib.unquote(b64decode(urllib.unquote(payload)).decode())
d = dict(data.split("=") for data in payload.split('&'))
return d
def build_login_URL(self, nonce, redirect_uri):
data = {
'nonce': nonce,
'return_sso_url': redirect_uri
}
payload = urllib.urlencode(data)
payload = b64encode(payload.encode())
sig = hmac.new(self.__secret_key.encode(), payload, sha256).hexdigest()
return '/session/sso_provider?' + urllib.urlencode({'sso': payload, 'sig': sig})

View File

@@ -1,16 +1,17 @@
import cStringIO as StringIO import cStringIO as StringIO
import datetime import datetime
import re import re
from decimal import Decimal
from django.contrib import messages from django.contrib import messages
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.db.models import Count, Sum, F, FloatField, Q, Value
from django.http import Http404, HttpResponseRedirect from django.http import Http404, HttpResponseRedirect
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import get_object_or_404 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.views import generic from django.views import generic
from django.db.models import Q
from z3c.rml import rml2pdf from z3c.rml import rml2pdf
from RIGS import models from RIGS import models
@@ -30,19 +31,8 @@ class InvoiceIndex(generic.ListView):
return context return context
def get_queryset(self): def get_queryset(self):
# Manual query is the only way I have found to do this efficiently. Not ideal but needs must query = self.model.objects.outstanding().select_related('event', 'event__organisation', 'event__person',
sql = "SELECT * FROM " \ 'event__venue', 'event__mic')
"(SELECT " \
"(SELECT COUNT(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payment_count\", " \
"(SELECT SUM(ei.cost * ei.quantity) FROM \"RIGS_eventitem\" AS ei WHERE ei.event_id=\"RIGS_invoice\".event_id) AS \"cost\", " \
"(SELECT SUM(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payments\", " \
"\"RIGS_invoice\".\"id\", \"RIGS_invoice\".\"event_id\", \"RIGS_invoice\".\"invoice_date\", \"RIGS_invoice\".\"void\" FROM \"RIGS_invoice\") " \
"AS sub " \
"WHERE (((cost > 0.0) AND (payment_count=0)) OR (cost - payments) <> 0.0) AND void = '0'" \
"ORDER BY invoice_date"
query = self.model.objects.raw(sql)
return query return query
@@ -94,6 +84,7 @@ class InvoiceVoid(generic.View):
return HttpResponseRedirect(reverse_lazy('invoice_list')) return HttpResponseRedirect(reverse_lazy('invoice_list'))
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': object.pk})) return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': object.pk}))
class InvoiceDelete(generic.DeleteView): class InvoiceDelete(generic.DeleteView):
model = models.Invoice model = models.Invoice
@@ -114,6 +105,7 @@ class InvoiceDelete(generic.DeleteView):
def get_success_url(self): def get_success_url(self):
return self.request.POST.get('next') return self.request.POST.get('next')
class InvoiceArchive(generic.ListView): class InvoiceArchive(generic.ListView):
model = models.Invoice model = models.Invoice
template_name = 'RIGS/invoice_list_archive.html' template_name = 'RIGS/invoice_list_archive.html'
@@ -122,7 +114,7 @@ class InvoiceArchive(generic.ListView):
class InvoiceWaiting(generic.ListView): class InvoiceWaiting(generic.ListView):
model = models.Event model = models.Event
# paginate_by = 25 paginate_by = 25
template_name = 'RIGS/event_invoice.html' template_name = 'RIGS/event_invoice.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):

View File

@@ -3,7 +3,6 @@ from django import forms
from django.utils import formats from django.utils import formats
from django.conf import settings from django.conf import settings
from django.core import serializers from django.core import serializers
from django.core.exceptions import ValidationError
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AuthenticationForm, PasswordResetForm from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AuthenticationForm, PasswordResetForm
from registration.forms import RegistrationFormUniqueEmail from registration.forms import RegistrationFormUniqueEmail
from captcha.fields import ReCaptchaField from captcha.fields import ReCaptchaField
@@ -28,16 +27,6 @@ class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail):
raise forms.ValidationError("These initials are already in use. Please supply different initials.") raise forms.ValidationError("These initials are already in use. Please supply different initials.")
return self.cleaned_data['initials'] return self.cleaned_data['initials']
def clean_first_name(self):
if self.cleaned_data["first_name"].strip() == '':
raise ValidationError("First name is required.")
return self.cleaned_data["first_name"]
def clean_last_name(self):
if self.cleaned_data["last_name"].strip() == '':
raise ValidationError("Last name is required.")
return self.cleaned_data["last_name"]
# Login form # Login form
class PasswordReset(PasswordResetForm): class PasswordReset(PasswordResetForm):

View File

@@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0024_auto_20160229_2042'),
]
operations = [
migrations.AddField(
model_name='profile',
name='avatar_template',
field=models.CharField(max_length=255, null=True, editable=False, blank=True),
),
]

View File

@@ -1,11 +1,11 @@
import datetime import datetime
import hashlib import hashlib
import pytz
import random import random
import string import string
from collections import Counter from collections import Counter
from decimal import Decimal from decimal import Decimal
import pytz
import reversion import reversion
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
@@ -22,7 +22,6 @@ class Profile(AbstractUser):
initials = models.CharField(max_length=5, unique=True, null=True, blank=False) initials = models.CharField(max_length=5, unique=True, null=True, blank=False)
phone = models.CharField(max_length=13, null=True, blank=True) phone = models.CharField(max_length=13, null=True, blank=True)
api_key = models.CharField(max_length=40, blank=True, editable=False, null=True) api_key = models.CharField(max_length=40, blank=True, editable=False, null=True)
avatar_template = models.CharField(max_length=255, blank=True, editable=False, null=True)
@classmethod @classmethod
def make_api_key(cls): def make_api_key(cls):
@@ -34,13 +33,8 @@ class Profile(AbstractUser):
@property @property
def profile_picture(self): def profile_picture(self):
url = "" url = ""
if settings.DISCOURSE_API_KEY is not None:
if self.avatar_template:
return settings.DISCOURSE_HOST+self.avatar_template.format(size=500)
if settings.USE_GRAVATAR or settings.USE_GRAVATAR is None: if settings.USE_GRAVATAR or settings.USE_GRAVATAR is None:
url = "https://www.gravatar.com/avatar/" + hashlib.md5(self.email).hexdigest() + "?d=wavatar&s=500" url = "https://www.gravatar.com/avatar/" + hashlib.md5(self.email).hexdigest() + "?d=wavatar&s=500"
return url return url
@property @property
@@ -507,14 +501,34 @@ class EventCrew(models.Model):
notes = models.TextField(blank=True, null=True) notes = models.TextField(blank=True, null=True)
class InvoiceManager(models.Manager):
def outstanding(self):
return self.annotate(
_payment_total=models.Sum(models.F('payment__amount'))
).annotate(
_sum_total=models.Sum(models.F('event__items__cost') * models.F('event__items__quantity'),
output_field=models.DecimalField(decimal_places=2))
# ).annotate(
# _balance=models.ExpressionWrapper(models.F('_sum_total') - models.F('_payment_total'),
# models.DecimalField(decimal_places=2))
# ).filter(
# models.Q(_balance__isnull=True) |
# ~models.Q(_sum_total=models.F('_payment_total'))
)
@python_2_unicode_compatible @python_2_unicode_compatible
class Invoice(models.Model): class Invoice(models.Model):
event = models.OneToOneField('Event') event = models.OneToOneField('Event')
invoice_date = models.DateField(auto_now_add=True) invoice_date = models.DateField(auto_now_add=True)
void = models.BooleanField(default=False) void = models.BooleanField(default=False)
objects = InvoiceManager()
@property @property
def sum_total(self): def sum_total(self):
if getattr(self, '_sum_total', None):
return Decimal(getattr(self, '_sum_total'))
return self.event.sum_total return self.event.sum_total
@property @property
@@ -523,6 +537,10 @@ class Invoice(models.Model):
@property @property
def payment_total(self): def payment_total(self):
if hasattr(self, '_payment_total') and hasattr(self, '_payment_count'):
if getattr(self, '_payment_count') == 0:
return Decimal(0.00)
return Decimal(getattr(self, '_payment_total', 0.00))
total = self.payment_set.aggregate(total=models.Sum('amount'))['total'] total = self.payment_set.aggregate(total=models.Sum('amount'))['total']
if total: if total:
return total return total

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 KiB

View File

@@ -23,7 +23,7 @@
<div class="list-group-item default"></div> <div class="list-group-item default"></div>
<a class="list-group-item" href="//members.nottinghamtec.co.uk/forum" target="_blank"><span class="glyphicon glyphicon-link"></span> TEC Forum</a> <a class="list-group-item" href="https://forum.nottinghamtec.co.uk" target="_blank"><span class="glyphicon glyphicon-link"></span> TEC Forum</a>
<a class="list-group-item" href="//members.nottinghamtec.co.uk/wiki" target="_blank"><span class="glyphicon glyphicon-link"></span> TEC Wiki</a> <a class="list-group-item" href="//members.nottinghamtec.co.uk/wiki" target="_blank"><span class="glyphicon glyphicon-link"></span> TEC Wiki</a>
<a class="list-group-item" href="http://members.nottinghamtec.co.uk/wiki/images/2/22/Event_Risk_Assesment.pdf" target="_blank"><span class="glyphicon glyphicon-link"></span> Pre-Event Risk Assessment</a> <a class="list-group-item" href="http://members.nottinghamtec.co.uk/wiki/images/2/22/Event_Risk_Assesment.pdf" target="_blank"><span class="glyphicon glyphicon-link"></span> Pre-Event Risk Assessment</a>
<a class="list-group-item" href="//members.nottinghamtec.co.uk/price" target="_blank"><span class="glyphicon glyphicon-link"></span> Price List</a> <a class="list-group-item" href="//members.nottinghamtec.co.uk/price" target="_blank"><span class="glyphicon glyphicon-link"></span> Price List</a>

View File

@@ -133,20 +133,6 @@
{% endif %} {% endif %}
</dd> </dd>
</dl> </dl>
<h4>Linked to {{object.social_auth.count}} Forum Account(s)</h4>
{% if object.social_auth.count > 0 %}
<a href="{% url 'unlink_forum' %}" class="btn btn-danger">
Unlink Forum Account(s) <span class="glyphicon glyphicon-pencil"></span>
</a>
{% else %}
<a href="{% url "social:begin" "discourse" %}" class="btn btn-success">
Link Forum Account <span class="glyphicon glyphicon-pencil"></span>
</a>
{% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>

View File

@@ -1,55 +0,0 @@
{% extends 'base.html' %}
{% load widget_tweaks %}
{% block title %}Associate{% endblock %}
{% block content %}
<div class="col-sm-10 col-sm-offset-1">
<div class="text-center">
<h1>R<small>ig</small> I<small>nformation</small> G<small>athering</small> S<small>ystem</small></h1>
</div>
<h2 class="text-center">Welcome <strong>{{details.username}}</strong></h2>
<h4 class="text-center">This is the first time you've visited RIGS with your forum account, so we need a few details to get you set up</h4>
<hr/>
{% if alreadyLoggedIn %}
<h2 class="text-center">You are logged in to RIGS as <strong>{{loggedInUser.username}}</strong></h2>
<div class="col-sm-8 col-sm-offset-2">
<form action="", method="post">{% csrf_token %}
<input type="hidden" name="UseCurrentAccount" value="1"/>
<button type="submit" class="btn btn-lg btn-primary center btn-block">Link Forum account to RIGS Account</button>
</form>
<a class="btn btn-lg btn-warning center btn-block" href="{% url 'logout' %}" role="button">Logout</a>
</div>
{% else %}
<h4 class="text-center"><a class="btn btn-info" href="{{login_url}}" role="button">I already have a RIGS account</a></h4>
{% if form.errors or supplement_form.errors %}
<div class="alert alert-danger">
{{form.errors}}
{{supplement_form.errors}}
</div>
{% endif %}
<div class="col-sm-8 col-sm-offset-2">
<form action="" method="post" class="form-horizontal" role="form">{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label for="{{ field.id_for_label }}" class="control-label col-sm-4">{{ field.label }}</label>
<div class="controls col-sm-8">
{% render_field field class+="form-control" placeholder=field.label %}
</div>
</div>
{% endfor %}
<p><input type="submit" value="Register" class="btn btn-primary pull-right"></p>
</form>
</div>
{% endif %}
</div>
{% endblock %}

View File

@@ -16,136 +16,136 @@ from selenium.webdriver.support.ui import WebDriverWait
from RIGS import models from RIGS import models
# class UserRegistrationTest(LiveServerTestCase): class UserRegistrationTest(LiveServerTestCase):
# def setUp(self): def setUp(self):
# self.browser = webdriver.Firefox() self.browser = webdriver.Firefox()
# self.browser.implicitly_wait(3) # Set implicit wait session wide self.browser.implicitly_wait(3) # Set implicit wait session wide
# os.environ['RECAPTCHA_TESTING'] = 'True' os.environ['RECAPTCHA_TESTING'] = 'True'
# def tearDown(self): def tearDown(self):
# self.browser.quit() self.browser.quit()
# os.environ['RECAPTCHA_TESTING'] = 'False' os.environ['RECAPTCHA_TESTING'] = 'False'
# def test_registration(self): def test_registration(self):
# # Navigate to the registration page # Navigate to the registration page
# self.browser.get(self.live_server_url + '/user/register/') self.browser.get(self.live_server_url + '/user/register/')
# title_text = self.browser.find_element_by_tag_name('h3').text title_text = self.browser.find_element_by_tag_name('h3').text
# self.assertIn("User Registration", title_text) self.assertIn("User Registration", title_text)
# # Check the form invites correctly # Check the form invites correctly
# username = self.browser.find_element_by_id('id_username') username = self.browser.find_element_by_id('id_username')
# self.assertEqual(username.get_attribute('placeholder'), 'Username') self.assertEqual(username.get_attribute('placeholder'), 'Username')
# email = self.browser.find_element_by_id('id_email') email = self.browser.find_element_by_id('id_email')
# self.assertEqual(email.get_attribute('placeholder'), 'E-mail') self.assertEqual(email.get_attribute('placeholder'), 'E-mail')
# # If this is correct we don't need to test it later # If this is correct we don't need to test it later
# self.assertEqual(email.get_attribute('type'), 'email') self.assertEqual(email.get_attribute('type'), 'email')
# password1 = self.browser.find_element_by_id('id_password1') password1 = self.browser.find_element_by_id('id_password1')
# self.assertEqual(password1.get_attribute('placeholder'), 'Password') self.assertEqual(password1.get_attribute('placeholder'), 'Password')
# self.assertEqual(password1.get_attribute('type'), 'password') self.assertEqual(password1.get_attribute('type'), 'password')
# password2 = self.browser.find_element_by_id('id_password2') password2 = self.browser.find_element_by_id('id_password2')
# self.assertEqual( self.assertEqual(
# password2.get_attribute('placeholder'), 'Password confirmation') password2.get_attribute('placeholder'), 'Password confirmation')
# self.assertEqual(password2.get_attribute('type'), 'password') self.assertEqual(password2.get_attribute('type'), 'password')
# first_name = self.browser.find_element_by_id('id_first_name') first_name = self.browser.find_element_by_id('id_first_name')
# self.assertEqual(first_name.get_attribute('placeholder'), 'First name') self.assertEqual(first_name.get_attribute('placeholder'), 'First name')
# last_name = self.browser.find_element_by_id('id_last_name') last_name = self.browser.find_element_by_id('id_last_name')
# self.assertEqual(last_name.get_attribute('placeholder'), 'Last name') self.assertEqual(last_name.get_attribute('placeholder'), 'Last name')
# initials = self.browser.find_element_by_id('id_initials') initials = self.browser.find_element_by_id('id_initials')
# self.assertEqual(initials.get_attribute('placeholder'), 'Initials') self.assertEqual(initials.get_attribute('placeholder'), 'Initials')
# phone = self.browser.find_element_by_id('id_phone') phone = self.browser.find_element_by_id('id_phone')
# self.assertEqual(phone.get_attribute('placeholder'), 'Phone') self.assertEqual(phone.get_attribute('placeholder'), 'Phone')
# # Fill the form out incorrectly # Fill the form out incorrectly
# username.send_keys('TestUsername') username.send_keys('TestUsername')
# email.send_keys('test@example.com') email.send_keys('test@example.com')
# password1.send_keys('correcthorsebatterystaple') password1.send_keys('correcthorsebatterystaple')
# # deliberate mistake # deliberate mistake
# password2.send_keys('correcthorsebatterystapleerror') password2.send_keys('correcthorsebatterystapleerror')
# first_name.send_keys('John') first_name.send_keys('John')
# last_name.send_keys('Smith') last_name.send_keys('Smith')
# initials.send_keys('JS') initials.send_keys('JS')
# phone.send_keys('0123456789') phone.send_keys('0123456789')
# self.browser.execute_script( self.browser.execute_script(
# "return jQuery('#g-recaptcha-response').val('PASSED')") "return jQuery('#g-recaptcha-response').val('PASSED')")
# # Submit incorrect form # Submit incorrect form
# submit = self.browser.find_element_by_xpath("//input[@type='submit']") submit = self.browser.find_element_by_xpath("//input[@type='submit']")
# submit.click() submit.click()
# # Restablish error fields # Restablish error fields
# password1 = self.browser.find_element_by_id('id_password1') password1 = self.browser.find_element_by_id('id_password1')
# password2 = self.browser.find_element_by_id('id_password2') password2 = self.browser.find_element_by_id('id_password2')
# # Read what the error is # Read what the error is
# alert = self.browser.find_element_by_css_selector( alert = self.browser.find_element_by_css_selector(
# 'div.alert-danger').text 'div.alert-danger').text
# self.assertIn("password fields didn't match", alert) self.assertIn("password fields didn't match", alert)
# # Passwords should be empty # Passwords should be empty
# self.assertEqual(password1.get_attribute('value'), '') self.assertEqual(password1.get_attribute('value'), '')
# self.assertEqual(password2.get_attribute('value'), '') self.assertEqual(password2.get_attribute('value'), '')
# # Correct error # Correct error
# password1.send_keys('correcthorsebatterystaple') password1.send_keys('correcthorsebatterystaple')
# password2.send_keys('correcthorsebatterystaple') password2.send_keys('correcthorsebatterystaple')
# self.browser.execute_script( self.browser.execute_script(
# "return jQuery('#g-recaptcha-response').val('PASSED')") "return jQuery('#g-recaptcha-response').val('PASSED')")
# # Submit again # Submit again
# password2.send_keys(Keys.ENTER) password2.send_keys(Keys.ENTER)
# # Check we have a success message # Check we have a success message
# alert = self.browser.find_element_by_css_selector( alert = self.browser.find_element_by_css_selector(
# 'div.alert-success').text 'div.alert-success').text
# self.assertIn('register', alert) self.assertIn('register', alert)
# self.assertIn('email', alert) self.assertIn('email', alert)
# # Check Email # Check Email
# self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
# email = mail.outbox[0] email = mail.outbox[0]
# self.assertIn('John Smith "JS" activation required', email.subject) self.assertIn('John Smith "JS" activation required', email.subject)
# urls = re.findall( urls = re.findall(
# 'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', email.body) 'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', email.body)
# self.assertEqual(len(urls), 1) self.assertEqual(len(urls), 1)
# mail.outbox = [] # empty this for later mail.outbox = [] # empty this for later
# # Follow link # Follow link
# self.browser.get(urls[0]) # go to the first link self.browser.get(urls[0]) # go to the first link
# # Complete registration # Complete registration
# title_text = self.browser.find_element_by_tag_name('h2').text title_text = self.browser.find_element_by_tag_name('h2').text
# self.assertIn('Complete', title_text) self.assertIn('Complete', title_text)
# # Test login # Test login
# self.browser.get(self.live_server_url + '/user/login') self.browser.get(self.live_server_url + '/user/login')
# username = self.browser.find_element_by_id('id_username') username = self.browser.find_element_by_id('id_username')
# self.assertEqual(username.get_attribute('placeholder'), 'Username') self.assertEqual(username.get_attribute('placeholder'), 'Username')
# password = self.browser.find_element_by_id('id_password') password = self.browser.find_element_by_id('id_password')
# self.assertEqual(password.get_attribute('placeholder'), 'Password') self.assertEqual(password.get_attribute('placeholder'), 'Password')
# self.assertEqual(password.get_attribute('type'), 'password') self.assertEqual(password.get_attribute('type'), 'password')
# username.send_keys('TestUsername') username.send_keys('TestUsername')
# password.send_keys('correcthorsebatterystaple') password.send_keys('correcthorsebatterystaple')
# self.browser.execute_script( self.browser.execute_script(
# "return jQuery('#g-recaptcha-response').val('PASSED')") "return jQuery('#g-recaptcha-response').val('PASSED')")
# password.send_keys(Keys.ENTER) password.send_keys(Keys.ENTER)
# # Check we are logged in # Check we are logged in
# udd = self.browser.find_element_by_class_name('navbar').text udd = self.browser.find_element_by_class_name('navbar').text
# self.assertIn('Hi John', udd) self.assertIn('Hi John', udd)
# # Check all the data actually got saved # Check all the data actually got saved
# profileObject = models.Profile.objects.all()[0] profileObject = models.Profile.objects.all()[0]
# self.assertEqual(profileObject.username, 'TestUsername') self.assertEqual(profileObject.username, 'TestUsername')
# self.assertEqual(profileObject.first_name, 'John') self.assertEqual(profileObject.first_name, 'John')
# self.assertEqual(profileObject.last_name, 'Smith') self.assertEqual(profileObject.last_name, 'Smith')
# self.assertEqual(profileObject.initials, 'JS') self.assertEqual(profileObject.initials, 'JS')
# self.assertEqual(profileObject.phone, '0123456789') self.assertEqual(profileObject.phone, '0123456789')
# self.assertEqual(profileObject.email, 'test@example.com') self.assertEqual(profileObject.email, 'test@example.com')
# # All is well # All is well
class EventTest(LiveServerTestCase): class EventTest(LiveServerTestCase):

View File

@@ -153,7 +153,6 @@ urlpatterns = patterns('',
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'),
url(r'^user/unlink_forum$', login_required(views.UnlinkForum.as_view(permanent=False)), name='unlink_forum'),
# ICS Calendar - API key authentication # ICS Calendar - API key authentication
url(r'^ical/(?P<api_pk>\d+)/(?P<api_key>\w+)/rigs.ics$', api_key_required(ical.CalendarICS()), name="ics_calendar"), url(r'^ical/(?P<api_pk>\d+)/(?P<api_key>\w+)/rigs.ics$', api_key_required(ical.CalendarICS()), name="ics_calendar"),

View File

@@ -382,12 +382,3 @@ class ResetApiKey(generic.RedirectView):
self.request.user.save() self.request.user.save()
return reverse_lazy('profile_detail') return reverse_lazy('profile_detail')
class UnlinkForum(generic.RedirectView):
def get_redirect_url(self, *args, **kwargs):
for link in self.request.user.social_auth.all():
link.delete()
self.request.user.save()
return reverse_lazy('profile_detail')

View File

@@ -12,23 +12,17 @@ django-widget-tweaks==1.3
gunicorn==19.3.0 gunicorn==19.3.0
icalendar==3.9.0 icalendar==3.9.0
lxml==3.4.4 lxml==3.4.4
oauthlib==2.0.0
Pillow==2.8.1 Pillow==2.8.1
psycopg2==2.6 psycopg2==2.6
Pygments==2.0.2 Pygments==2.0.2
PyJWT==1.4.2
PyPDF2==1.24 PyPDF2==1.24
python-dateutil==2.4.2 python-dateutil==2.4.2
python-openid==2.2.5
python-social-auth==0.2.21
pytz==2015.4 pytz==2015.4
raven==5.8.1 raven==5.8.1
reportlab==3.1.44 reportlab==3.1.44
requests==2.11.1
requests-oauthlib==0.7.0
selenium==2.53.6 selenium==2.53.6
simplejson==3.7.2 simplejson==3.7.2
six==1.10.0 six==1.9.0
sqlparse==0.1.15 sqlparse==0.1.15
static3==0.6.1 static3==0.6.1
svg2rlg==0.3 svg2rlg==0.3

View File

@@ -1,5 +1,4 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static from staticfiles %}
{% block title %}Login{% endblock %} {% block title %}Login{% endblock %}
@@ -7,43 +6,5 @@
<div class="text-center"> <div class="text-center">
<h1>R<small>ig</small> I<small>nformation</small> G<small>athering</small> S<small>ystem</small></h1> <h1>R<small>ig</small> I<small>nformation</small> G<small>athering</small> S<small>ystem</small></h1>
</div> </div>
{% include 'registration/loginform.html' %}
<div class="panel-group">
{% url "social:complete" "discourse" as completeUrl %}
{% if not request.GET.next == completeUrl %}
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
Login with TEC Forum
</h4>
</div>
<div id="forumLogin">
<div class="panel-body" style="text-align:center;">
<a class="btn btn-default" href="{% url "social:begin" "discourse" %}?next={{request.GET.next}}">
<h4>Login using</h4>
<img src="{% static "imgs/forum-logo.gif" %}" width=200></img>
</a>
</div>
</div>
</div>
{% endif %}
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
Login with RIGS Credentials
</h4>
</div>
<div class="panel-body">
<div class="panel-body">
{% include 'registration/loginform.html' %}
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View File

@@ -13,7 +13,7 @@
{% render_field form.password class+="form-control" placeholder=form.password.label %} {% render_field form.password class+="form-control" placeholder=form.password.label %}
</div> </div>
<div class="text-right"> <div class="text-right">
{# <a href="{% url 'registration_register' %}" class="btn">Register</a> #} <a href="{% url 'registration_register' %}" class="btn">Register</a>
<a href="{% url 'password_reset' %}" class="btn">Forgotten Password</a> <a href="{% url 'password_reset' %}" class="btn">Forgotten Password</a>
<input type="submit" value="Login" class="btn btn-primary"/> <input type="submit" value="Login" class="btn btn-primary"/>
<input type="hidden" name="next" value="{{ next }}"/> <input type="hidden" name="next" value="{{ next }}"/>