Skip to content

Commit a85bde9

Browse files
authored
feat: add custom form json column to sessions, speaker, attendees (#6435)
1 parent eb8d721 commit a85bde9

File tree

11 files changed

+102
-12
lines changed

11 files changed

+102
-12
lines changed

app/api/helpers/validations.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from app import get_settings
2+
from app.api.helpers.exceptions import UnprocessableEntity
3+
4+
5+
def validate_complex_fields_json(self, data, original_data):
6+
if data.get('complex_field_values'):
7+
if any(((not isinstance(i, (str, bool, int, float))) and i is not None)
8+
for i in data['complex_field_values'].values()):
9+
raise UnprocessableEntity({'pointer': '/data/attributes/complex_field_values'},
10+
"Only flattened JSON of form {key: value} where value is a string, "
11+
"integer, float, bool or null is permitted for this field")
12+
13+
if len(data['complex_field_values']) > get_settings()['max_complex_custom_fields']:
14+
raise UnprocessableEntity({'pointer': '/data/attributes/complex_field_values'},
15+
"A maximum of {} complex custom form fields are currently supported"
16+
.format(get_settings()['max_complex_custom_fields']))

app/api/schema/attendees.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
from app.api.helpers.utilities import dasherize
55
from app.api.schema.base import SoftDeletionSchema
6+
from app.api.helpers.validations import validate_complex_fields_json
7+
from marshmallow import validates_schema
68

79

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

24+
@validates_schema(pass_original=True)
25+
def validate_json(self, data, original_data):
26+
validate_complex_fields_json(self, data, original_data)
27+
2228
id = fields.Str(dump_only=True)
2329
firstname = fields.Str(required=True)
2430
lastname = fields.Str(required=True)
@@ -52,6 +58,7 @@ class Meta:
5258
attendee_notes = fields.Str(allow_none=True)
5359
is_checked_out = fields.Boolean()
5460
pdf_url = fields.Url(dump_only=True)
61+
complex_field_values = fields.Dict(allow_none=True)
5562
event = Relationship(attribute='event',
5663
self_view='v1.attendee_event',
5764
self_view_kwargs={'id': '<id>'},

app/api/schema/sessions.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from app.api.schema.base import SoftDeletionSchema
1212
from app.models.session import Session
1313
from utils.common import use_defaults
14+
from app.api.helpers.validations import validate_complex_fields_json
1415

1516

1617
@use_defaults()
@@ -29,7 +30,7 @@ class Meta:
2930
inflect = dasherize
3031

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

70+
validate_complex_fields_json(self, data, original_data)
71+
6972
id = fields.Str(dump_only=True)
7073
title = fields.Str(required=True)
7174
subtitle = fields.Str(allow_none=True)
@@ -90,6 +93,7 @@ def validate_date(self, data, original_data):
9093
last_modified_at = fields.DateTime(dump_only=True)
9194
send_email = fields.Boolean(load_only=True, allow_none=True)
9295
average_rating = fields.Float(dump_only=True)
96+
complex_field_values = fields.Dict(allow_none=True)
9397
microlocation = Relationship(attribute='microlocation',
9498
self_view='v1.session_microlocation',
9599
self_view_kwargs={'id': '<id>'},

app/api/schema/settings.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ class Meta:
3030
# Order Expiry Time
3131
order_expiry_time = fields.Integer(allow_none=False, default=15, validate=lambda n: 1 <= n <= 60)
3232

33+
# Maximum number of complex custom fields allowed for a given form
34+
max_complex_custom_fields = fields.Integer(allow_none=False, default=30, validate=lambda n: 1 <= n <= 30)
35+
3336
# Google Analytics
3437
analytics_key = fields.Str(allow_none=True)
3538

@@ -156,7 +159,7 @@ class Meta:
156159
in_client_secret = fields.Str(allow_none=True)
157160

158161
#
159-
# Payment Gateway
162+
# Payment Gateways
160163
#
161164

162165
# Stripe Credantials

app/api/schema/speakers.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from marshmallow_jsonapi import fields
22
from marshmallow_jsonapi.flask import Relationship
3-
3+
from marshmallow import validates_schema
44
from app.api.helpers.utilities import dasherize
55
from app.api.schema.base import SoftDeletionSchema
66
from utils.common import use_defaults
7+
from app.api.helpers.validations import validate_complex_fields_json
78

89

910
@use_defaults()
@@ -12,6 +13,10 @@ class SpeakerSchema(SoftDeletionSchema):
1213
Speaker Schema based on Speaker Model
1314
"""
1415

16+
@validates_schema(pass_original=True)
17+
def validate_json(self, data, original_data):
18+
validate_complex_fields_json(self, data, original_data)
19+
1520
class Meta:
1621
"""
1722
Meta class for speaker schema
@@ -46,6 +51,7 @@ class Meta:
4651
gender = fields.Str(allow_none=True)
4752
heard_from = fields.Str(allow_none=True)
4853
sponsorship_required = fields.Str(allow_none=True)
54+
complex_field_values = fields.Dict(allow_none=True)
4955
event = Relationship(attribute='event',
5056
self_view='v1.speaker_event',
5157
self_view_kwargs={'id': '<id>'},

app/models/session.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class Session(SoftDeletionModel):
5454
last_modified_at = db.Column(db.DateTime(timezone=True), default=datetime.datetime.utcnow)
5555
send_email = db.Column(db.Boolean, nullable=True)
5656
is_locked = db.Column(db.Boolean, default=False, nullable=False)
57+
complex_field_values = db.Column(db.JSON)
5758

5859
def __init__(self,
5960
title=None,
@@ -83,7 +84,9 @@ def __init__(self,
8384
submitted_at=None,
8485
last_modified_at=None,
8586
send_email=None,
86-
is_locked=False):
87+
is_locked=False,
88+
complex_field_values=None
89+
):
8790

8891
if speakers is None:
8992
speakers = []
@@ -116,6 +119,7 @@ def __init__(self,
116119
self.last_modified_at = datetime.datetime.now(pytz.utc)
117120
self.send_email = send_email
118121
self.is_locked = is_locked
122+
self.complex_field_values = complex_field_values
119123

120124
@staticmethod
121125
def get_service_name():

app/models/setting.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ class Setting(db.Model):
3333
# Order Expiry Time in Minutes
3434
order_expiry_time = db.Column(db.Integer, default=15, nullable=False)
3535

36+
# Maximum number of complex custom fields allowed for a given form
37+
max_complex_custom_fields = db.Column(db.Integer, default=30, nullable=False)
38+
3639
#
3740
# STORAGE
3841
#
@@ -250,7 +253,9 @@ def __init__(self,
250253
admin_billing_state=None,
251254
admin_billing_zip=None,
252255
admin_billing_additional_info=None,
253-
order_expiry_time=None):
256+
order_expiry_time=None,
257+
max_complex_custom_fields=30
258+
):
254259
self.app_environment = app_environment
255260
self.aws_key = aws_key
256261
self.aws_secret = aws_secret
@@ -313,7 +318,6 @@ def __init__(self,
313318
self.paypal_sandbox_client = paypal_sandbox_client
314319
self.paypal_sandbox_secret = paypal_sandbox_secret
315320

316-
317321
# Omise Credentials
318322
self.omise_mode = omise_mode
319323
self.omise_test_public = omise_test_public
@@ -352,6 +356,8 @@ def __init__(self,
352356
# Order Expiry Time in Minutes
353357
self.order_expiry_time = order_expiry_time
354358

359+
self.max_complex_custom_fields = max_complex_custom_fields
360+
355361
@hybrid_property
356362
def is_paypal_activated(self):
357363
if self.paypal_mode == 'sandbox' and self.paypal_sandbox_client and self.paypal_sandbox_secret:

app/models/speaker.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class Speaker(SoftDeletionModel):
3131
gender = db.Column(db.String)
3232
heard_from = db.Column(db.String)
3333
sponsorship_required = db.Column(db.Text)
34+
complex_field_values = db.Column(db.JSON)
3435
event_id = db.Column(db.Integer, db.ForeignKey('events.id', ondelete='CASCADE'))
3536
user_id = db.Column(db.Integer, db.ForeignKey('users.id', ondelete='SET NULL'))
3637

@@ -61,7 +62,8 @@ def __init__(self,
6162
sponsorship_required=None,
6263
event_id=None,
6364
user_id=None,
64-
deleted_at=None):
65+
deleted_at=None,
66+
complex_field_values=None):
6567
self.name = name
6668
self.photo_url = photo_url
6769
self.thumbnail_image_url = thumbnail_image_url
@@ -88,7 +90,8 @@ def __init__(self,
8890
self.sponsorship_required = sponsorship_required
8991
self.event_id = event_id
9092
self.user_id = user_id
91-
self.deleted_at = deleted_at
93+
self.deleted_at = deleted_at,
94+
self.complex_field_values = complex_field_values
9295

9396
@staticmethod
9497
def get_service_name():

app/models/ticket_holder.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class TicketHolder(SoftDeletionModel):
4646
checkout_times = db.Column(db.String)
4747
attendee_notes = db.Column(db.String)
4848
event_id = db.Column(db.Integer, db.ForeignKey('events.id', ondelete='CASCADE'))
49+
complex_field_values = db.Column(db.JSON)
4950
user = db.relationship('User', foreign_keys=[email], primaryjoin='User.email == TicketHolder.email', viewonly=True,
5051
backref='attendees')
5152

@@ -83,7 +84,8 @@ def __init__(self,
8384
order_id=None,
8485
pdf_url=None,
8586
event_id=None,
86-
deleted_at=None):
87+
deleted_at=None,
88+
complex_field_values=None):
8789
self.firstname = firstname
8890
self.lastname = lastname
8991
self.email = email
@@ -118,6 +120,7 @@ def __init__(self,
118120
self.pdf_url = pdf_url
119121
self.event_id = event_id
120122
self.deleted_at = deleted_at
123+
self.complex_field_values = complex_field_values
121124

122125
def __repr__(self):
123126
return '<TicketHolder %r>' % self.id
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""empty message
2+
3+
Revision ID: 7c32ba647a18
4+
Revises: ebfe89366d48
5+
Create Date: 2019-09-02 09:34:18.949897
6+
7+
"""
8+
9+
from alembic import op
10+
import sqlalchemy as sa
11+
import sqlalchemy_utils
12+
13+
14+
# revision identifiers, used by Alembic.
15+
revision = '7c32ba647a18'
16+
down_revision = 'ebfe89366d48'
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.add_column('sessions', sa.Column('complex_field_values', sa.JSON(), nullable=True))
22+
op.add_column('sessions_version', sa.Column('complex_field_values', sa.JSON(), autoincrement=False, nullable=True))
23+
op.add_column('speaker', sa.Column('complex_field_values', sa.JSON(), nullable=True))
24+
op.add_column('ticket_holders', sa.Column('complex_field_values', sa.JSON(), nullable=True))
25+
op.add_column('settings', sa.Column('max_complex_custom_fields', sa.Integer(), server_default='30', nullable=False))
26+
# ### end Alembic commands ###
27+
28+
29+
def downgrade():
30+
# ### commands auto generated by Alembic - please adjust! ###
31+
op.drop_column('ticket_holders', 'complex_field_values')
32+
op.drop_column('speaker', 'complex_field_values')
33+
op.drop_column('sessions', 'complex_field_values')
34+
op.drop_column('sessions_version', 'complex_field_values')
35+
op.drop_column('settings', 'max_complex_custom_fields')
36+
37+
38+
# ### end Alembic commands ###

0 commit comments

Comments
 (0)