Skip to content
This repository was archived by the owner on Apr 28, 2020. It is now read-only.

Linter fixes and switch to UnicodeText #362

Merged
merged 2 commits into from
Jun 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion features/steps/failed_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def given_existing_user(context):
context.test_user = dict(
username='bobthehacker',
password='bobthehacker'
)
)


@when("the nonexisting user tries to log in")
Expand Down
2 changes: 1 addition & 1 deletion features/steps/failed_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def given_new_user(context):
username='alyssa',
password='alyssa',
confirm_password='alyssa'
)
)
# registering the test user
context.browser.visit('/register')
assert context.browser.find_element_by_name('csrf_token').is_enabled()
Expand Down
4 changes: 2 additions & 2 deletions features/steps/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def given_existing_user(context):
username='alyssa',
password='alyssa',
confirm_password='alyssa'
)
)

context.browser.visit('/register')
assert context.browser.find_element_by_name('csrf_token').is_enabled()
Expand All @@ -26,7 +26,7 @@ def when_login_form_submit(context):
context.login_data = {
'username': context.test_user['username'],
'password': context.test_user['password']
}
}
wait = ui.WebDriverWait(context.browser, 30)

context.browser.visit('/login')
Expand Down
4 changes: 2 additions & 2 deletions features/steps/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def given_new_user(context):
username='alyssa',
password='alyssa',
confirm_password='alyssa'
)
)


@when('a new user submits the registration form with the proper details')
Expand All @@ -29,4 +29,4 @@ def when_form_submit(context):
def then_user_registered(context):
user = User.get(username=context.test_user['username'])
assert user is not None
assert len(user.emailclaims) is 1
assert len(user.emailclaims) == 1
80 changes: 7 additions & 73 deletions lastuser_core/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,80 +1,14 @@
# -*- coding: utf-8 -*-
# flake8: noqa

# Imported from here by other models
from coaster.sqlalchemy import TimestampMixin, BaseMixin, BaseScopedNameMixin, UuidMixin # NOQA
from coaster.sqlalchemy import TimestampMixin, BaseMixin, BaseScopedNameMixin, UuidMixin
from coaster.db import db

TimestampMixin.__with_timezone__ = True

from .user import * # NOQA
from .session import * # NOQA
from .client import * # NOQA
from .notification import * # NOQA


def getuser(name):
if '@' in name:
# TODO: This should have used UserExternalId.__at_username_services__,
# but this bit has traditionally been for Twitter only. Fix pending.
if name.startswith('@'):
extid = UserExternalId.get(service='twitter', username=name[1:])
if extid and extid.user.is_active:
return extid.user
else:
return None
else:
useremail = UserEmail.get(email=name)
if useremail and useremail.user is not None and useremail.user.is_active:
return useremail.user
# No verified email id. Look for an unverified id; return first found
results = UserEmailClaim.all(email=name)
if results and results[0].user.is_active:
return results[0].user
return None
else:
return User.get(username=name)


def getextid(service, userid):
return UserExternalId.get(service=service, userid=userid)


def merge_users(user1, user2):
"""
Merge two user accounts and return the new user account.
"""
# Always keep the older account and merge from the newer account
if user1.created_at < user2.created_at:
keep_user, merge_user = user1, user2
else:
keep_user, merge_user = user2, user1

# 1. Release the username
if not keep_user.username:
if merge_user.username:
# Flush before re-assigning to avoid dupe name constraint
username = merge_user.username
merge_user.username = None
db.session.flush()
keep_user.username = username
merge_user.username = None

# 2. Inspect all tables for foreign key references to merge_user and switch to keep_user.
for model in db.Model.__subclasses__():
if model != User:
# a. This is a model and it's not the User model. Does it have a migrate_user classmethod?
if hasattr(model, 'migrate_user'):
model.migrate_user(olduser=merge_user, newuser=keep_user)
# b. No migrate_user? Does it have a user_id column?
elif hasattr(model, 'user_id') and hasattr(model, 'query'):
for row in model.query.filter_by(user_id=merge_user.id).all():
row.user_id = keep_user.id
# 3. Add merge_user's uuid to olduserids. Commit session.
db.session.add(UserOldId(id=merge_user.uuid, user=keep_user))
# 4. Mark merge_user as merged. Commit session.
merge_user.status = USER_STATUS.MERGED
# 5. Commit all of this
db.session.commit()

# 6. Return keep_user.
return keep_user
from .user import *
from .session import *
from .client import *
from .notification import *
from .helpers import *
22 changes: 11 additions & 11 deletions lastuser_core/models/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def _scope_get(self):
if not self._scope:
return ()
else:
return tuple(sorted([t.strip() for t in self._scope.replace('\r', ' ').replace('\n', ' ').split(u' ') if t]))
return tuple(sorted(self._scope.split()))

def _scope_set(self, value):
if isinstance(value, basestring):
Expand Down Expand Up @@ -66,15 +66,15 @@ class Client(ScopeMixin, BaseMixin, db.Model):
#: Confidential or public client? Public has no secret key
confidential = db.Column(db.Boolean, nullable=False)
#: Website
website = db.Column(db.Unicode(250), nullable=False)
website = db.Column(db.UnicodeText, nullable=False)
#: Namespace: determines inter-app resource access
namespace = db.Column(db.Unicode(250), nullable=True, unique=True)
namespace = db.Column(db.UnicodeText, nullable=True, unique=True)
#: Redirect URI
redirect_uri = db.Column(db.Unicode(250), nullable=True, default=u'')
redirect_uri = db.Column(db.UnicodeText, nullable=True, default=u'')
#: Back-end notification URI
notification_uri = db.Column(db.Unicode(250), nullable=True, default=u'')
notification_uri = db.Column(db.UnicodeText, nullable=True, default=u'')
#: Front-end notification URI
iframe_uri = db.Column(db.Unicode(250), nullable=True, default=u'')
iframe_uri = db.Column(db.UnicodeText, nullable=True, default=u'')
#: Active flag
active = db.Column(db.Boolean, nullable=False, default=True)
#: Allow anyone to login to this app?
Expand Down Expand Up @@ -219,8 +219,8 @@ class UserFlashMessage(BaseMixin, db.Model):
user = db.relationship(User, primaryjoin=user_id == User.id,
backref=db.backref('flashmessages', cascade='delete, delete-orphan'))
seq = db.Column(db.Integer, default=0, nullable=False)
category = db.Column(db.Unicode(20), nullable=False)
message = db.Column(db.Unicode(250), nullable=False)
category = db.Column(db.UnicodeText, nullable=False)
message = db.Column(db.UnicodeText, nullable=False)


class Resource(BaseScopedNameMixin, db.Model):
Expand Down Expand Up @@ -317,7 +317,7 @@ class AuthCode(ScopeMixin, BaseMixin, db.Model):
session_id = db.Column(None, db.ForeignKey('user_session.id'), nullable=True)
session = db.relationship(UserSession)
code = db.Column(db.String(44), default=newsecret, nullable=False)
redirect_uri = db.Column(db.Unicode(1024), nullable=False)
redirect_uri = db.Column(db.UnicodeText, nullable=False)
used = db.Column(db.Boolean, default=False, nullable=False)

def is_valid(self):
Expand Down Expand Up @@ -543,7 +543,7 @@ class UserClientPermissions(BaseMixin, db.Model):
client = db.relationship(Client, primaryjoin=client_id == Client.id,
backref=db.backref('user_permissions', cascade='all, delete-orphan'))
#: The permissions as a string of tokens
access_permissions = db.Column('permissions', db.Unicode(250), default=u'', nullable=False)
access_permissions = db.Column('permissions', db.UnicodeText, default=u'', nullable=False)

# Only one assignment per user and client
__table_args__ = (db.UniqueConstraint('user_id', 'client_id'), {})
Expand Down Expand Up @@ -588,7 +588,7 @@ class TeamClientPermissions(BaseMixin, db.Model):
client = db.relationship(Client, primaryjoin=client_id == Client.id,
backref=db.backref('team_permissions', cascade='all, delete-orphan'))
#: The permissions as a string of tokens
access_permissions = db.Column('permissions', db.Unicode(250), default=u'', nullable=False)
access_permissions = db.Column('permissions', db.UnicodeText, default=u'', nullable=False)

# Only one assignment per team and client
__table_args__ = (db.UniqueConstraint('team_id', 'client_id'), {})
Expand Down
73 changes: 73 additions & 0 deletions lastuser_core/models/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-

from .user import db, User, UserEmail, UserEmailClaim, UserExternalId, UserOldId, USER_STATUS

__all__ = ['getuser', 'getextid', 'merge_users']


def getuser(name):
if '@' in name:
# TODO: This should have used UserExternalId.__at_username_services__,
# but this bit has traditionally been for Twitter only. Fix pending.
if name.startswith('@'):
extid = UserExternalId.get(service='twitter', username=name[1:])
if extid and extid.user.is_active:
return extid.user
else:
return None
else:
useremail = UserEmail.get(email=name)
if useremail and useremail.user is not None and useremail.user.is_active:
return useremail.user
# No verified email id. Look for an unverified id; return first found
results = UserEmailClaim.all(email=name)
if results and results[0].user.is_active:
return results[0].user
return None
else:
return User.get(username=name)


def getextid(service, userid):
return UserExternalId.get(service=service, userid=userid)


def merge_users(user1, user2):
"""
Merge two user accounts and return the new user account.
"""
# Always keep the older account and merge from the newer account
if user1.created_at < user2.created_at:
keep_user, merge_user = user1, user2
else:
keep_user, merge_user = user2, user1

# 1. Release the username
if not keep_user.username:
if merge_user.username:
# Flush before re-assigning to avoid dupe name constraint
username = merge_user.username
merge_user.username = None
db.session.flush()
keep_user.username = username
merge_user.username = None

# 2. Inspect all tables for foreign key references to merge_user and switch to keep_user.
for model in db.Model.__subclasses__():
if model != User:
# a. This is a model and it's not the User model. Does it have a migrate_user classmethod?
if hasattr(model, 'migrate_user'):
model.migrate_user(olduser=merge_user, newuser=keep_user)
# b. No migrate_user? Does it have a user_id column?
elif hasattr(model, 'user_id') and hasattr(model, 'query'):
for row in model.query.filter_by(user_id=merge_user.id).all():
row.user_id = keep_user.id
# 3. Add merge_user's uuid to olduserids. Commit session.
db.session.add(UserOldId(id=merge_user.uuid, user=keep_user))
# 4. Mark merge_user as merged. Commit session.
merge_user.status = USER_STATUS.MERGED
# 5. Commit all of this
db.session.commit()

# 6. Return keep_user.
return keep_user
45 changes: 21 additions & 24 deletions lastuser_core/models/notification.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,43 @@
# -*- coding: utf-8 -*-

from sqlalchemy.ext.declarative import declared_attr
from coaster.utils import LabeledEnum
from baseframe import __
from ..registry import OrderedDict
from . import db, BaseMixin, BaseScopedNameMixin
from .user import User, UserEmail, UserPhone
from .client import Client
from . import db, BaseMixin

__all__ = ['SMSMessage', 'SMS_STATUS']


# --- Flags -------------------------------------------------------------------

class SMS_STATUS(LabeledEnum):
QUEUED = (0, __(u"Queued"))
PENDING = (1, __(u"Pending"))
DELIVERED = (2, __(u"Delivered"))
FAILED = (3, __(u"Failed"))
UNKNOWN = (4, __(u"Unknown"))
QUEUED = (0, __(u"Queued")) # NOQA: E221
PENDING = (1, __(u"Pending")) # NOQA: E221
DELIVERED = (2, __(u"Delivered")) # NOQA: E221
FAILED = (3, __(u"Failed")) # NOQA: E221
UNKNOWN = (4, __(u"Unknown")) # NOQA: E221


class NOTIFICATION_FLAGS(LabeledEnum):
DELIVERY = (0, __(u"Delivery"))
READ = (1, __(u"Read"))
BOUNCE = (2, __(u"Bounce"))
DELIVERY = (0, __(u"Delivery")) # NOQA: E221
READ = (1, __(u"Read")) # NOQA: E221
BOUNCE = (2, __(u"Bounce")) # NOQA: E221


class NOTIFICATION_TYPE(LabeledEnum):
MANDATORY = (0, u'mandatory', __(u"Mandatory")) # Mandatory service announcement
TRANSACTIONAL = (1, u'transactional', __(u"Transactional")) # Result of user activity
ALERT = (2, u'alert', __(u"Alert")) # Periodic alert based on set criteria
MASS = (3, u'mass', __(u"Mass")) # Mass mail from the service provider
MANDATORY = (0, u'mandatory', __(u"Mandatory")) # Mandatory service announcement # NOQA: E221,E241
TRANSACTIONAL = (1, u'transactional', __(u"Transactional")) # Result of user activity # NOQA: E221,E241
ALERT = (2, u'alert', __(u"Alert")) # Periodic alert based on set criteria # NOQA: E221,E241
MASS = (3, u'mass', __(u"Mass")) # Mass mail from the service provider # NOQA: E221,E241


# A note on frequency: scheduling/batching is done by Lastuser, not by the client app
class NOTIFICATION_FREQUENCY(LabeledEnum):
IMMEDIATE = (0, u'immed', __(u"Immediately")) # Alert user immediately
DELAYED = (1, u'delay', __(u"Delayed")) # Send after a timeout, allowing app to cancel (tentative)
DAILY = (2, u'daily', __(u"Batched daily")) # Send a daily digest
WEEKLY = (3, u'weekly', __(u"Batched weekly")) # Send a weekly digest
MONTHLY = (4, u'monthly', __(u"Batched monthly")) # Send a monthly digest
IMMEDIATE = (0, u'immed', __(u"Immediately")) # Alert user immediately # NOQA: E221,E241
DELAYED = (1, u'delay', __(u"Delayed")) # Send after a timeout, allowing app to cancel (tentative) # NOQA: E221,E241
DAILY = (2, u'daily', __(u"Batched daily")) # Send a daily digest # NOQA: E221,E241
WEEKLY = (3, u'weekly', __(u"Batched weekly")) # Send a weekly digest # NOQA: E221,E241
MONTHLY = (4, u'monthly', __(u"Batched monthly")) # Send a monthly digest # NOQA: E221,E241


# --- Transport Channels ------------------------------------------------------
Expand Down Expand Up @@ -108,13 +105,13 @@ class SMSMessage(BaseMixin, db.Model):
__tablename__ = 'smsmessage'
# Phone number that the message was sent to
phone_number = db.Column(db.String(15), nullable=False)
transaction_id = db.Column(db.Unicode(40), unique=True, nullable=True)
transaction_id = db.Column(db.UnicodeText, unique=True, nullable=True)
# The message itself
message = db.Column(db.UnicodeText, nullable=False)
# Flags
status = db.Column(db.Integer, default=0, nullable=False)
status_at = db.Column(db.TIMESTAMP(timezone=True), nullable=True)
fail_reason = db.Column(db.Unicode(25), nullable=True)
fail_reason = db.Column(db.UnicodeText, nullable=True)


# class ChannelMixin(object):
Expand All @@ -124,7 +121,7 @@ class SMSMessage(BaseMixin, db.Model):
# Preferred channels for sending this notification class (in order of preference).
# Only listed channels are available for delivery of this notification.
# """
# return db.Column('channels', db.Unicode(250), default=u'', nullable=False)
# return db.Column('channels', db.UnicodeText, default=u'', nullable=False)

# def _channels_get(self):
# return [c.strip() for c in self._channels.replace(u'\r', u' ').replace(u'\n', u' ').split(u' ') if c]
Expand Down
2 changes: 1 addition & 1 deletion lastuser_core/models/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class UserSession(BaseMixin, db.Model):
user = db.relationship(User, backref=db.backref('sessions', cascade='all, delete-orphan', lazy='dynamic'))

ipaddr = db.Column(db.String(45), nullable=False)
user_agent = db.Column(db.Unicode(250), nullable=False)
user_agent = db.Column(db.UnicodeText, nullable=False)

accessed_at = db.Column(db.TIMESTAMP(timezone=True), nullable=False)
revoked_at = db.Column(db.TIMESTAMP(timezone=True), nullable=True)
Expand Down
Loading