7 Commits

Author SHA1 Message Date
b3cb42dbef Create the @feature_lock decorator 2018-07-23 13:04:04 +02:00
f7d807370d Create the @beta decorator 2018-07-23 13:04:04 +02:00
a97d884f42 Add pytest-cov as a developer dependency 2018-07-23 13:04:04 +02:00
11bd30e01f [Bugfix] Fix broken tests 2018-07-23 13:04:04 +02:00
4c3ec0564f [Bugfix] Fix registration
Allow POSTing to the registration endpoint
2018-07-23 13:04:04 +02:00
f8e3c748c0 [Bugfix] Fix jumping to first steps page upon initial login
This was missing from when the first steps view got moved to the accounts blueprint.
2018-07-23 13:04:04 +02:00
9e7ea29f5e [Lint] Make PyLint happy again 2018-07-23 12:35:57 +02:00
10 changed files with 129 additions and 33 deletions

View File

@@ -18,6 +18,7 @@ flask-caching = "*"
[dev-packages]
pylint = "*"
pytest = "*"
pytest-cov = "*"
[requires]
python_version = "3.6"

66
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "e4313bc9baef5cb187176951d45094fe1de4ccba0d15ab58efbac21b6434f255"
"sha256": "01a306fc25c75731af3fcf119a20d92c24fe5be9ddd8be2901b830df10bfb294"
},
"pipfile-spec": 6,
"requires": {
@@ -284,10 +284,10 @@
"develop": {
"astroid": {
"hashes": [
"sha256:8704779744963d56a2625ec2949eb150bd499fc099510161ddbb2b64e2d98138",
"sha256:add3fd690e7c1fe92436d17be461feeaa173e6f33e0789734310334da0f30027"
"sha256:0a0c484279a5f08c9bcedd6fa9b42e378866a7dcc695206b92d59dc9f2d9760d",
"sha256:218e36cf8d98a42f16214e8670819ce307fa707d1dcf7f9af84c7aede1febc7f"
],
"version": "==2.0"
"version": "==2.0.1"
},
"atomicwrites": {
"hashes": [
@@ -303,6 +303,50 @@
],
"version": "==18.1.0"
},
"coverage": {
"hashes": [
"sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba",
"sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed",
"sha256:104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a",
"sha256:10a46017fef60e16694a30627319f38a2b9b52e90182dddb6e37dcdab0f4bf95",
"sha256:15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd",
"sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640",
"sha256:1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2",
"sha256:23d341cdd4a0371820eb2b0bd6b88f5003a7438bbedb33688cd33b8eae59affd",
"sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162",
"sha256:2a5b73210bad5279ddb558d9a2bfedc7f4bf6ad7f3c988641d83c40293deaec1",
"sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508",
"sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249",
"sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694",
"sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a",
"sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287",
"sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1",
"sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000",
"sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1",
"sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e",
"sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5",
"sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062",
"sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba",
"sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc",
"sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc",
"sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99",
"sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653",
"sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c",
"sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558",
"sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f",
"sha256:9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4",
"sha256:ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91",
"sha256:b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d",
"sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9",
"sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd",
"sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d",
"sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6",
"sha256:e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77",
"sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80",
"sha256:f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e"
],
"version": "==4.5.1"
},
"isort": {
"hashes": [
"sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af",
@@ -377,11 +421,11 @@
},
"pylint": {
"hashes": [
"sha256:248a7b19138b22e6390cba71adc0cb03ac6dd75a25d3544f03ea1728fa20e8f4",
"sha256:9cd70527ef3b099543eeabeb5c80ff325d86b477aa2b3d49e264e12d12153bc8"
"sha256:2c90a24bee8fae22ac98061c896e61f45c5b73c2e0511a4bf53f99ba56e90434",
"sha256:454532779425098969b8f54ab0f056000b883909f69d05905ea114df886e3251"
],
"index": "pypi",
"version": "==2.0.0"
"version": "==2.0.1"
},
"pytest": {
"hashes": [
@@ -391,6 +435,14 @@
"index": "pypi",
"version": "==3.6.3"
},
"pytest-cov": {
"hashes": [
"sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d",
"sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec"
],
"index": "pypi",
"version": "==2.5.1"
},
"six": {
"hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",

View File

@@ -107,7 +107,7 @@ class CalendarSocialApp(Flask, RoutedMixin):
if current_user.is_authenticated and \
not current_user.profile and \
request.endpoint != 'first_steps':
request.endpoint != 'account.first_steps':
return redirect(url_for('account.first_steps'))
return None

View File

@@ -46,7 +46,7 @@ class AccountBlueprint(Blueprint, RoutedMixin):
app.register_blueprint(self, url_prefix=url_prefix)
@staticmethod
@RoutedMixin.route('/register')
@RoutedMixin.route('/register', methods=['POST', 'GET'])
def register_account():
"""View for user registration

View File

@@ -21,7 +21,7 @@ from datetime import timedelta
import pickle
from uuid import uuid4
from flask import current_app, has_request_context, request, session
from flask import has_request_context, request as flask_request, session as flask_session
from flask.sessions import SessionInterface, SessionMixin
from flask_caching import Cache
from werkzeug.datastructures import CallbackDict
@@ -46,7 +46,7 @@ class CachedSession(CallbackDict, SessionMixin): # pylint: disable=too-many-anc
self.__modifying = True
if has_request_context():
self['ip'] = request.remote_addr
self['ip'] = flask_request.remote_addr
self.modified = True
@@ -59,6 +59,9 @@ class CachedSession(CallbackDict, SessionMixin): # pylint: disable=too-many-anc
@property
def user(self):
"""The user this session belongs to
"""
from calsocial.models import User
if 'user_id' not in self:
@@ -87,11 +90,11 @@ class CachedSessionInterface(SessionInterface):
return str(uuid4())
@staticmethod
def get_cache_expiration_time(app, session):
def get_cache_expiration_time(app, sess):
"""Get the expiration time of the cache entry
"""
if session.permanent:
if sess.permanent:
return app.permanent_session_lifetime
return timedelta(days=1)
@@ -147,7 +150,10 @@ class CachedSessionInterface(SessionInterface):
domain=domain)
def delete_session(self, sid):
if has_request_context() and session.sid == sid:
"""Delete the session with ``sid`` as its session ID
"""
if has_request_context() and flask_session.sid == sid:
raise ValueError('Will not delete the current session')
cache.delete(self.prefix + sid)

View File

@@ -219,7 +219,7 @@ class GregorianCalendar(CalendarSystem):
end_timestamp = start_timestamp + timedelta(days=1)
events = events.filter((Event.start_time <= end_timestamp) &
(Event.end_time >= start_timestamp)) \
(Event.end_time >= start_timestamp)) \
.order_by('start_time', 'end_time')
if user is None:

View File

@@ -18,6 +18,11 @@
"""
from contextlib import contextmanager
from functools import wraps
from flask import flash, redirect, url_for
from flask_babelex import gettext as _
from flask_security import current_user
@contextmanager
def force_locale(locale):
@@ -89,6 +94,9 @@ class RoutedMixin:
"""
def register_routes(self):
"""Register all routes that were marked with :meth:`route`
"""
for attr_name in self.__dir__():
attr = getattr(self, attr_name)
@@ -116,3 +124,41 @@ class RoutedMixin:
return func
return decorator
def beta(func):
"""Decorator to hide beta features from non-beta testers
"""
@wraps(func)
def decorated(*args, **kwargs): # pylint: disable=missing-docstring
if current_user.settings['beta'] != 'True':
flash(_('Join the beta testers to enable this functionality!'))
return redirect(url_for('account.settings'))
return func(*args, **kwargs)
return decorated
def feature_lock(feature): # pylint: disable=missing-return-doc,missing-return-type-doc
"""Decorator to lock a feature
:param feature: the name of a feature
:type feature: str
"""
def decorator(func): # pylint: disable=missing-docstring
@wraps(func)
def decorated(*args, **kwargs): # pylint: disable=missing-docstring
from calsocial.models import AppState
if AppState[f'feature:{feature}'] == 'true':
return func(*args, **kwargs)
return 'Feature locked'
return decorated
return decorator

View File

@@ -25,4 +25,4 @@ def test_index_no_login(client):
"""
page = client.get('/')
assert b'Welcome to Calendar.social' in page.data
assert b'Peek inside' in page.data

View File

@@ -23,14 +23,6 @@ from calsocial.models import db, User
from helpers import client, login
def test_index_no_login(client):
"""Test the main page without logging in
"""
page = client.get('/')
assert b'Welcome to Calendar.social' in page.data
def test_login_invalid_user(client):
"""Test logging in with a non-existing user
"""
@@ -81,4 +73,4 @@ def test_login_first_steps(client):
assert page.location == 'http://localhost/'
page = client.get('/')
assert page.location == 'http://localhost/first-steps'
assert page.location == 'http://localhost/accounts/first-steps'

View File

@@ -27,21 +27,21 @@ def test_register_page(client):
"""Test the registration page
"""
page = client.get('/register')
page = client.get('/accounts/register')
assert b'Register</button>' in page.data
def test_register_post_empty(client):
"""Test sending empty registration data
"""
page = client.post('/register', data={})
page = client.post('/accounts/register', data={})
assert b'This field is required' in page.data
def test_register_invalid_email(client):
"""Test sending an invalid email address
"""
page = client.post('/register', data={
page = client.post('/accounts/register', data={
'username': 'test',
'email': 'test',
'password': 'password',
@@ -53,7 +53,7 @@ def test_register_password_mismatch(client):
"""Test sending different password for registration
"""
page = client.post('/register', data={
page = client.post('/accounts/register', data={
'username': 'test',
'email': 'test@example.com',
'password': 'password',
@@ -65,13 +65,12 @@ def test_register(client):
"""Test user registration
"""
page = client.post('/register', data={
page = client.post('/accounts/register', data={
'username': 'test',
'email': 'test@example.com',
'password': 'password',
'password_retype': 'password',
})
print(page.data)
assert page.status_code == 302
assert page.location == 'http://localhost/'
@@ -90,7 +89,7 @@ def test_register_existing_username(client):
db.session.add(user)
db.session.commit()
page = client.post('/register', data={
page = client.post('/accounts/register', data={
'username': 'test',
'email': 'test2@example.com',
'password': 'password',
@@ -107,7 +106,7 @@ def test_register_existing_email(client):
db.session.add(user)
db.session.commit()
page = client.post('/register', data={
page = client.post('/accounts/register', data={
'username': 'tester',
'email': 'test@example.com',
'password': 'password',