Skip to content
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
16 changes: 16 additions & 0 deletions app/api/helpers/validations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from app import get_settings
from app.api.helpers.exceptions import UnprocessableEntity


def validate_complex_fields_json(self, data, original_data):
if data.get('complex_field_values'):
if any(((not isinstance(i, (str, bool, int, float))) and i is not None)
for i in data['complex_field_values'].values()):
raise UnprocessableEntity({'pointer': '/data/attributes/complex_field_values'},
"Only flattened JSON of form {key: value} where value is a string, "
"integer, float, bool or null is permitted for this field")

if len(data['complex_field_values']) > get_settings()['max_complex_custom_fields']:
raise UnprocessableEntity({'pointer': '/data/attributes/complex_field_values'},
"A maximum of {} complex custom form fields are currently supported"
.format(get_settings()['max_complex_custom_fields']))
7 changes: 7 additions & 0 deletions app/api/schema/attendees.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from app.api.helpers.utilities import dasherize
from app.api.schema.base import SoftDeletionSchema
from app.api.helpers.validations import validate_complex_fields_json
from marshmallow import validates_schema


class AttendeeSchemaPublic(SoftDeletionSchema):
Expand All @@ -19,6 +21,10 @@ class Meta:
self_view_kwargs = {'id': '<id>'}
inflect = dasherize

@validates_schema(pass_original=True)
def validate_json(self, data, original_data):
validate_complex_fields_json(self, data, original_data)

id = fields.Str(dump_only=True)
firstname = fields.Str(required=True)
lastname = fields.Str(required=True)
Expand Down Expand Up @@ -52,6 +58,7 @@ class Meta:
attendee_notes = fields.Str(allow_none=True)
is_checked_out = fields.Boolean()
pdf_url = fields.Url(dump_only=True)
complex_field_values = fields.Dict(allow_none=True)
event = Relationship(attribute='event',
self_view='v1.attendee_event',
self_view_kwargs={'id': '<id>'},
Expand Down
6 changes: 5 additions & 1 deletion app/api/schema/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from app.api.schema.base import SoftDeletionSchema
from app.models.session import Session
from utils.common import use_defaults
from app.api.helpers.validations import validate_complex_fields_json


@use_defaults()
Expand All @@ -29,7 +30,7 @@ class Meta:
inflect = dasherize

@validates_schema(pass_original=True)
def validate_date(self, data, original_data):
def validate_fields(self, data, original_data):
if 'id' in original_data['data']:
try:
session = Session.query.filter_by(id=original_data['data']['id']).one()
Expand Down Expand Up @@ -66,6 +67,8 @@ def validate_date(self, data, original_data):
if not has_access('is_coorganizer', event_id=data['event']):
return ForbiddenException({'source': ''}, 'Co-organizer access is required.')

validate_complex_fields_json(self, data, original_data)

id = fields.Str(dump_only=True)
title = fields.Str(required=True)
subtitle = fields.Str(allow_none=True)
Expand All @@ -90,6 +93,7 @@ def validate_date(self, data, original_data):
last_modified_at = fields.DateTime(dump_only=True)
send_email = fields.Boolean(load_only=True, allow_none=True)
average_rating = fields.Float(dump_only=True)
complex_field_values = fields.Dict(allow_none=True)
microlocation = Relationship(attribute='microlocation',
self_view='v1.session_microlocation',
self_view_kwargs={'id': '<id>'},
Expand Down
5 changes: 4 additions & 1 deletion app/api/schema/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class Meta:
# Order Expiry Time
order_expiry_time = fields.Integer(allow_none=False, default=15, validate=lambda n: 1 <= n <= 60)

# Maximum number of complex custom fields allowed for a given form
max_complex_custom_fields = fields.Integer(allow_none=False, default=30, validate=lambda n: 1 <= n <= 30)

# Google Analytics
analytics_key = fields.Str(allow_none=True)

Expand Down Expand Up @@ -156,7 +159,7 @@ class Meta:
in_client_secret = fields.Str(allow_none=True)

#
# Payment Gateway
# Payment Gateways
#

# Stripe Credantials
Expand Down
8 changes: 7 additions & 1 deletion app/api/schema/speakers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from marshmallow_jsonapi import fields
from marshmallow_jsonapi.flask import Relationship

from marshmallow import validates_schema
from app.api.helpers.utilities import dasherize
from app.api.schema.base import SoftDeletionSchema
from utils.common import use_defaults
from app.api.helpers.validations import validate_complex_fields_json


@use_defaults()
Expand All @@ -12,6 +13,10 @@ class SpeakerSchema(SoftDeletionSchema):
Speaker Schema based on Speaker Model
"""

@validates_schema(pass_original=True)
def validate_json(self, data, original_data):
validate_complex_fields_json(self, data, original_data)

class Meta:
"""
Meta class for speaker schema
Expand Down Expand Up @@ -46,6 +51,7 @@ class Meta:
gender = fields.Str(allow_none=True)
heard_from = fields.Str(allow_none=True)
sponsorship_required = fields.Str(allow_none=True)
complex_field_values = fields.Dict(allow_none=True)
event = Relationship(attribute='event',
self_view='v1.speaker_event',
self_view_kwargs={'id': '<id>'},
Expand Down
6 changes: 5 additions & 1 deletion app/models/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class Session(SoftDeletionModel):
last_modified_at = db.Column(db.DateTime(timezone=True), default=datetime.datetime.utcnow)
send_email = db.Column(db.Boolean, nullable=True)
is_locked = db.Column(db.Boolean, default=False, nullable=False)
complex_field_values = db.Column(db.JSON)

def __init__(self,
title=None,
Expand Down Expand Up @@ -83,7 +84,9 @@ def __init__(self,
submitted_at=None,
last_modified_at=None,
send_email=None,
is_locked=False):
is_locked=False,
complex_field_values=None
):

if speakers is None:
speakers = []
Expand Down Expand Up @@ -116,6 +119,7 @@ def __init__(self,
self.last_modified_at = datetime.datetime.now(pytz.utc)
self.send_email = send_email
self.is_locked = is_locked
self.complex_field_values = complex_field_values

@staticmethod
def get_service_name():
Expand Down
10 changes: 8 additions & 2 deletions app/models/setting.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class Setting(db.Model):
# Order Expiry Time in Minutes
order_expiry_time = db.Column(db.Integer, default=15, nullable=False)

# Maximum number of complex custom fields allowed for a given form
max_complex_custom_fields = db.Column(db.Integer, default=30, nullable=False)

#
# STORAGE
#
Expand Down Expand Up @@ -250,7 +253,9 @@ def __init__(self,
admin_billing_state=None,
admin_billing_zip=None,
admin_billing_additional_info=None,
order_expiry_time=None):
order_expiry_time=None,
max_complex_custom_fields=30
):
self.app_environment = app_environment
self.aws_key = aws_key
self.aws_secret = aws_secret
Expand Down Expand Up @@ -313,7 +318,6 @@ def __init__(self,
self.paypal_sandbox_client = paypal_sandbox_client
self.paypal_sandbox_secret = paypal_sandbox_secret


# Omise Credentials
self.omise_mode = omise_mode
self.omise_test_public = omise_test_public
Expand Down Expand Up @@ -352,6 +356,8 @@ def __init__(self,
# Order Expiry Time in Minutes
self.order_expiry_time = order_expiry_time

self.max_complex_custom_fields = max_complex_custom_fields

@hybrid_property
def is_paypal_activated(self):
if self.paypal_mode == 'sandbox' and self.paypal_sandbox_client and self.paypal_sandbox_secret:
Expand Down
7 changes: 5 additions & 2 deletions app/models/speaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Speaker(SoftDeletionModel):
gender = db.Column(db.String)
heard_from = db.Column(db.String)
sponsorship_required = db.Column(db.Text)
complex_field_values = db.Column(db.JSON)
event_id = db.Column(db.Integer, db.ForeignKey('events.id', ondelete='CASCADE'))
user_id = db.Column(db.Integer, db.ForeignKey('users.id', ondelete='SET NULL'))

Expand Down Expand Up @@ -61,7 +62,8 @@ def __init__(self,
sponsorship_required=None,
event_id=None,
user_id=None,
deleted_at=None):
deleted_at=None,
complex_field_values=None):
self.name = name
self.photo_url = photo_url
self.thumbnail_image_url = thumbnail_image_url
Expand All @@ -88,7 +90,8 @@ def __init__(self,
self.sponsorship_required = sponsorship_required
self.event_id = event_id
self.user_id = user_id
self.deleted_at = deleted_at
self.deleted_at = deleted_at,
self.complex_field_values = complex_field_values

@staticmethod
def get_service_name():
Expand Down
5 changes: 4 additions & 1 deletion app/models/ticket_holder.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class TicketHolder(SoftDeletionModel):
checkout_times = db.Column(db.String)
attendee_notes = db.Column(db.String)
event_id = db.Column(db.Integer, db.ForeignKey('events.id', ondelete='CASCADE'))
complex_field_values = db.Column(db.JSON)
user = db.relationship('User', foreign_keys=[email], primaryjoin='User.email == TicketHolder.email', viewonly=True,
backref='attendees')

Expand Down Expand Up @@ -83,7 +84,8 @@ def __init__(self,
order_id=None,
pdf_url=None,
event_id=None,
deleted_at=None):
deleted_at=None,
complex_field_values=None):
self.firstname = firstname
self.lastname = lastname
self.email = email
Expand Down Expand Up @@ -118,6 +120,7 @@ def __init__(self,
self.pdf_url = pdf_url
self.event_id = event_id
self.deleted_at = deleted_at
self.complex_field_values = complex_field_values

def __repr__(self):
return '<TicketHolder %r>' % self.id
Expand Down
38 changes: 38 additions & 0 deletions migrations/versions/rev-2019-09-02-09:34:18-7c32ba647a18_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""empty message

Revision ID: 7c32ba647a18
Revises: ebfe89366d48
Create Date: 2019-09-02 09:34:18.949897

"""

from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils


# revision identifiers, used by Alembic.
revision = '7c32ba647a18'
down_revision = 'ebfe89366d48'


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('sessions', sa.Column('complex_field_values', sa.JSON(), nullable=True))
op.add_column('sessions_version', sa.Column('complex_field_values', sa.JSON(), autoincrement=False, nullable=True))
op.add_column('speaker', sa.Column('complex_field_values', sa.JSON(), nullable=True))
op.add_column('ticket_holders', sa.Column('complex_field_values', sa.JSON(), nullable=True))
op.add_column('settings', sa.Column('max_complex_custom_fields', sa.Integer(), server_default='30', nullable=False))
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('ticket_holders', 'complex_field_values')
op.drop_column('speaker', 'complex_field_values')
op.drop_column('sessions', 'complex_field_values')
op.drop_column('sessions_version', 'complex_field_values')
op.drop_column('settings', 'max_complex_custom_fields')


# ### end Alembic commands ###
6 changes: 3 additions & 3 deletions tests/all/integration/api/validation/test_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_date_pass(self):
'starts_at': datetime(2099, 8, 4, 12, 30, 45).replace(tzinfo=timezone('UTC')),
'ends_at': datetime(2099, 9, 4, 12, 30, 45).replace(tzinfo=timezone('UTC'))
}
SessionSchema.validate_date(schema, data, original_data)
SessionSchema.validate_fields(schema, data, original_data)

def test_date_start_gt_end(self):
"""
Expand All @@ -45,7 +45,7 @@ def test_date_start_gt_end(self):
'ends_at': datetime(2099, 8, 4, 12, 30, 45).replace(tzinfo=timezone('UTC'))
}
with self.assertRaises(UnprocessableEntity):
SessionSchema.validate_date(schema, data, original_data)
SessionSchema.validate_fields(schema, data, original_data)

def test_date_db_populate(self):
"""
Expand All @@ -63,7 +63,7 @@ def test_date_db_populate(self):
}
}
data = {}
SessionSchema.validate_date(schema, data, original_data)
SessionSchema.validate_fields(schema, data, original_data)


if __name__ == '__main__':
Expand Down