Repository: fossasia/open-event-server

Issue:
Correct Event API
- `show_map` -> `is_map_shown`. Change data type to boolean. Default `false`.
- `has_session_speakers` -> `has_sessions_speakers`.
- `ticket_include` -> `is_ticketing_enabled`. (default: true)
- `tax_allow` -> `is_tax_enabled`. 
- `pay_by_paypal` -> `is_pay_by_paypal`
- `pay_by_stripe` -> `is_pay_by_stripe_enabled`
- `pay_by_bank` -> `is_pay_by_bank_enabled`
- `pay_by_cheque` -> `is_pay_by_cheque_enabled`
- `sponsors_enabled` -> `is_sponsors_enabled`
- `email` - remove from API, model and db. Not used.
- `state` - Make all state data lower-case.


Also update db field & model for all of the above without resulting in loss of any data. 



Discussion:
@niranjan94 
### Problem : 
- Changing data type of a column results in all values turning to **NULL** 

### Proposed Solutions
I have thought of a few methods of changing datatype without loss of data.
1. Using the bind method of **almebic** ( but you suggested that there could be even simpler method )
2. Creating a temporary column and then feeding data again to the newly created column from the temporary column. In the end deleting the temporary column.
3. Executing the raw query , cause when changing the type and to prevent loss we need to **cast** and use the statement `USING` like here.

```
    op.execute('ALTER TABLE events ALTER COLUMN show_map drop default')
    op.execute('ALTER TABLE events ALTER COLUMN show_map TYPE BOOLEAN USING (show_map::boolean)')
    op.alter_column('events', 'show_map', new_column_name='is_map_shown')
```
Currently I have tried the **1st** and **3rd** one and both are working fine.
3rd one is easier though.

Your suggestions.
I'm afk. Will reply in an hour or so.
But the key is..
First remove the default integer constraint
Then change the data type while converting the data at the same time. You
can do that in postgres.
Then set the default constrain for boolean.

On Sat, Jun 17, 2017, 1:11 PM Aseem Shrey <notifications@github.com> wrote:

> @niranjan94 <https://github.com/niranjan94>
> Problem :
>
>    - Changing data type of a column results in all values turning to
>    *NULL*
>
> Proposed Solutions
>
> I have thought of a few methods of changing datatype without loss of data.
>
>    1. Using the bind method of *almebic* ( but you suggested that there
>    could be even simpler method )
>    2. Creating a temporary column and then feeding data again to the
>    newly created column from the temporary column. In the end deleting the
>    temporary column.
>    3. Executing the raw query , cause when changing the type and to
>    prevent loss we need to *cast* and use the statement USING like here.
>
>     op.execute('ALTER TABLE events ALTER COLUMN show_map drop default')
>     op.execute('ALTER TABLE events ALTER COLUMN show_map TYPE BOOLEAN USING (show_map::boolean)')
>     op.alter_column('events', 'show_map', new_column_name='is_map_shown')
>
> Currently I have tried the *1st* and *3rd* one and both are working fine.
> 3rd one is easier though.
>
> Your suggestions.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/fossasia/open-event-orga-server/issues/3816#issuecomment-309199818>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/ACSwFIibckstka4-EMsSOaFGhy4Ump05ks5sE4M-gaJpZM4N8b9u>
> .
>

@niranjan94 
Yes did that.
Thanks . 
@niranjan94 
Have made all changes , check it once.
@niranjan94 
Can you check it now, please ?

Resolving patch:
diff --git a/app/api/events.py b/app/api/events.py
--- a/app/api/events.py
+++ b/app/api/events.py
@@ -49,18 +49,19 @@ def validate_timezone(self, data):
             starts_at = data['starts_at'].utcoffset().seconds
             ends_at = data['ends_at'].utcoffset().seconds
             if offset != starts_at:
-                raise UnprocessableEntity({'pointer': '/data/attributes/timezone'}, "timezone: {} does not match with the starts-at "
-                                                                     "offset {:02}:{:02}".
+                raise UnprocessableEntity({'pointer': '/data/attributes/timezone'},
+                                          "timezone: {} does not match with the starts-at "
+                                          "offset {:02}:{:02}".
                                           format(data['timezone'], starts_at // 3600, starts_at % 3600 // 60))
             if offset != ends_at:
-                raise UnprocessableEntity({'pointer': '/data/attributes/timezone'}, "timezone: {} does not match with the ends-at "
-                                                                     "offset {:02}:{:02}".
+                raise UnprocessableEntity({'pointer': '/data/attributes/timezone'},
+                                          "timezone: {} does not match with the ends-at "
+                                          "offset {:02}:{:02}".
                                           format(data['timezone'], ends_at // 3600, ends_at % 3600 // 60))
 
     id = fields.Str(dump_only=True)
     identifier = fields.Str(dump_only=True)
     name = fields.Str(required=True)
-    email = fields.Email()
     event_url = fields.Url(dump_only=True)
     starts_at = fields.DateTime(required=True, timezone=True)
     ends_at = fields.DateTime(required=True, timezone=True)
@@ -76,9 +77,9 @@ def validate_timezone(self, data):
     large_image_url = fields.Url(dump_only=True)
     icon_image_url = fields.Url(dump_only=True)
     organizer_name = fields.Str()
-    show_map = fields.Int()
+    is_map_shown = fields.Bool(default=False)
     organizer_description = fields.Str()
-    has_session_speakers = fields.Bool(default=False)
+    is_sessions_speakers_enabled = fields.Bool(default=False)
     privacy = fields.Str(default="public")
     state = fields.Str(default="Draft")
     type = fields.Str()
@@ -87,21 +88,21 @@ def validate_timezone(self, data):
     ticket_url = fields.Url()
     code_of_conduct = fields.Str()
     schedule_published_on = fields.DateTime()
-    ticket_include = fields.Bool(default=False)
+    is_ticketing_enabled = fields.Bool(default=True)
     deleted_at = fields.DateTime()
     payment_country = fields.Str()
     payment_currency = fields.Str()
     paypal_email = fields.Str()
-    tax_allow = fields.Bool(default=False)
-    pay_by_paypal = fields.Bool(default=False)
-    pay_by_stripe = fields.Bool(default=False)
-    pay_by_cheque = fields.Bool(default=False)
-    pay_by_bank = fields.Bool(default=False)
-    pay_onsite = fields.Bool(default=False)
+    is_tax_enabled = fields.Bool(default=False)
+    can_pay_by_paypal = fields.Bool(default=False)
+    can_pay_by_stripe = fields.Bool(default=False)
+    can_pay_by_cheque = fields.Bool(default=False)
+    can_pay_by_bank = fields.Bool(default=False)
+    can_pay_onsite = fields.Bool(default=False)
     cheque_details = fields.Str()
     bank_details = fields.Str()
     onsite_details = fields.Str()
-    sponsors_enabled = fields.Bool(default=False)
+    is_sponsors_enabled = fields.Bool(default=False)
     created_at = fields.DateTime(dump_only=True, timezone=True)
     pentabarf_url = fields.Url(dump_only=True)
     ical_url = fields.Url(dump_only=True)
diff --git a/app/models/event.py b/app/models/event.py
--- a/app/models/event.py
+++ b/app/models/event.py
@@ -47,7 +47,6 @@ class Event(db.Model):
     identifier = db.Column(db.String)
     name = db.Column(db.String, nullable=False)
     event_url = db.Column(db.String)
-    email = db.Column(db.String)
     logo_url = db.Column(db.String)
     starts_at = db.Column(db.DateTime(timezone=True), nullable=False)
     ends_at = db.Column(db.DateTime(timezone=True), nullable=False)
@@ -62,9 +61,9 @@ class Event(db.Model):
     large_image_url = db.Column(db.String)
     icon_image_url = db.Column(db.String)
     organizer_name = db.Column(db.String)
-    show_map = db.Column(db.Integer)
+    is_map_shown = db.Column(db.Integer)
     organizer_description = db.Column(db.String)
-    has_session_speakers = db.Column(db.Boolean, default=False)
+    is_sessions_speakers_enabled = db.Column(db.Boolean, default=False)
     track = db.relationship('Track', backref="event")
     microlocation = db.relationship('Microlocation', backref="event")
     session = db.relationship('Session', backref="event")
@@ -82,17 +81,17 @@ class Event(db.Model):
     db.UniqueConstraint('track.name')
     code_of_conduct = db.Column(db.String)
     schedule_published_on = db.Column(db.DateTime(timezone=True))
-    ticket_include = db.Column(db.Boolean, default=False)
+    is_ticketing_enabled = db.Column(db.Boolean, default=True)
     deleted_at = db.Column(db.DateTime(timezone=True))
     payment_country = db.Column(db.String)
     payment_currency = db.Column(db.String)
     paypal_email = db.Column(db.String)
-    tax_allow = db.Column(db.Boolean, default=False)
-    pay_by_paypal = db.Column(db.Boolean, default=False)
-    pay_by_stripe = db.Column(db.Boolean, default=False)
-    pay_by_cheque = db.Column(db.Boolean, default=False)
-    pay_by_bank = db.Column(db.Boolean, default=False)
-    pay_onsite = db.Column(db.Boolean, default=False)
+    is_tax_enabled = db.Column(db.Boolean, default=False)
+    can_pay_by_paypal = db.Column(db.Boolean, default=False)
+    can_pay_by_stripe = db.Column(db.Boolean, default=False)
+    can_pay_by_cheque = db.Column(db.Boolean, default=False)
+    can_pay_by_bank = db.Column(db.Boolean, default=False)
+    can_pay_onsite = db.Column(db.Boolean, default=False)
     cheque_details = db.Column(db.String)
     bank_details = db.Column(db.String)
     onsite_details = db.Column(db.String)
@@ -100,7 +99,7 @@ class Event(db.Model):
     pentabarf_url = db.Column(db.String)
     ical_url = db.Column(db.String)
     xcal_url = db.Column(db.String)
-    sponsors_enabled = db.Column(db.Boolean, default=False)
+    is_sponsors_enabled = db.Column(db.Boolean, default=False)
     discount_code = db.relationship('DiscountCode', backref='events')
 
     def __init__(self,
@@ -112,7 +111,6 @@ def __init__(self,
                  latitude=None,
                  longitude=None,
                  location_name=None,
-                 email=None,
                  description=None,
                  event_url=None,
                  thumbnail_image_url=None,
@@ -129,21 +127,21 @@ def __init__(self,
                  copyright=None,
                  code_of_conduct=None,
                  schedule_published_on=None,
-                 has_session_speakers=False,
-                 show_map=1,
+                 is_sessions_speakers_enabled=False,
+                 is_map_shown=1,
                  searchable_location_name=None,
-                 ticket_include=None,
+                 is_ticketing_enabled=None,
                  deleted_at=None,
                  payment_country=None,
                  payment_currency=None,
                  paypal_email=None,
                  speakers_call=None,
-                 pay_by_paypal=None,
-                 pay_by_stripe=None,
-                 pay_by_cheque=None,
+                 can_pay_by_paypal=None,
+                 can_pay_by_stripe=None,
+                 can_pay_by_cheque=None,
                  identifier=None,
-                 pay_by_bank=None,
-                 pay_onsite=None,
+                 can_pay_by_bank=None,
+                 can_pay_onsite=None,
                  cheque_details=None,
                  bank_details=None,
                  pentabarf_url=None,
@@ -155,7 +153,6 @@ def __init__(self,
 
         self.name = name
         self.logo = logo
-        self.email = email
         self.starts_at = starts_at
         self.ends_at = ends_at
         self.timezone = timezone
@@ -171,7 +168,7 @@ def __init__(self,
         self.organizer_name = organizer_name
         self.organizer_description = clean_up_string(organizer_description)
         self.state = state
-        self.show_map = show_map
+        self.is_map_shown = is_map_shown
         self.privacy = privacy
         self.type = type
         self.topic = topic
@@ -180,19 +177,19 @@ def __init__(self,
         self.ticket_url = ticket_url
         self.code_of_conduct = code_of_conduct
         self.schedule_published_on = schedule_published_on
-        self.has_session_speakers = has_session_speakers
+        self.is_sessions_speakers_enabled = is_sessions_speakers_enabled
         self.searchable_location_name = searchable_location_name
-        self.ticket_include = ticket_include
+        self.is_ticketing_enabled = is_ticketing_enabled
         self.deleted_at = deleted_at
         self.payment_country = payment_country
         self.payment_currency = payment_currency
         self.paypal_email = paypal_email
         self.speakers_call = speakers_call
-        self.pay_by_paypal = pay_by_paypal
-        self.pay_by_stripe = pay_by_stripe
-        self.pay_by_cheque = pay_by_cheque
-        self.pay_by_bank = pay_by_bank
-        self.pay_onsite = pay_onsite
+        self.can_pay_by_paypal = can_pay_by_paypal
+        self.can_pay_by_stripe = can_pay_by_stripe
+        self.can_pay_by_cheque = can_pay_by_cheque
+        self.can_pay_by_bank = can_pay_by_bank
+        self.can_pay_onsite = can_pay_onsite
         self.identifier = get_new_event_identifier()
         self.cheque_details = cheque_details
         self.bank_details = bank_details
@@ -252,7 +249,6 @@ def serialize(self):
             'latitude': self.latitude,
             'longitude': self.longitude,
             'location_name': self.location_name,
-            'email': self.email,
             'description': self.description,
             'event_url': self.event_url,
             'background_url': self.background_url,
@@ -261,7 +257,7 @@ def serialize(self):
             'icon': self.icon,
             'organizer_name': self.organizer_name,
             'organizer_description': self.organizer_description,
-            'has_session_speakers': self.has_session_speakers,
+            'is_sessions_speakers_enabled': self.is_sessions_speakers_enabled,
             'privacy': self.privacy,
             'ticket_url': self.ticket_url,
             'code_of_conduct': self.code_of_conduct,
diff --git a/migrations/versions/76c949b1235f_the_events_model_and_api_migrations.py b/migrations/versions/76c949b1235f_the_events_model_and_api_migrations.py
new file mode 100644
--- /dev/null
+++ b/migrations/versions/76c949b1235f_the_events_model_and_api_migrations.py
@@ -0,0 +1,88 @@
+"""The events model and api migrations
+
+Revision ID: 76c949b1235f
+Revises: c8de2a2757e5
+Create Date: 2017-06-18 23:17:01.159393
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '76c949b1235f'
+down_revision = 'c8de2a2757e5'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+    op.alter_column('events', 'pay_by_stripe', new_column_name='can_pay_by_stripe')
+    op.alter_column('events', 'pay_by_bank', new_column_name='can_pay_by_bank')
+    op.alter_column('events', 'pay_by_paypal', new_column_name='can_pay_by_paypal')
+    op.alter_column('events', 'pay_by_cheque', new_column_name='can_pay_by_cheque')
+    op.alter_column('events', 'pay_onsite', new_column_name='can_pay_onsite')
+    op.alter_column('events', 'tax_allow', new_column_name='is_tax_enabled')
+    op.alter_column('events', 'ticket_include', new_column_name='is_ticketing_enabled')
+    op.alter_column('events', 'has_session_speakers', new_column_name='has_sessions_speakers')
+    op.alter_column('events', 'sponsors_enabled', new_column_name='is_sponsors_enabled')
+    op.drop_column('events', 'email')
+
+    op.alter_column('events_version', 'pay_by_stripe', new_column_name='can_pay_by_stripe')
+    op.alter_column('events_version', 'pay_by_bank', new_column_name='can_pay_by_bank')
+    op.alter_column('events_version', 'pay_by_paypal', new_column_name='can_pay_by_paypal')
+    op.alter_column('events_version', 'pay_by_cheque', new_column_name='can_pay_by_cheque')
+    op.alter_column('events_version', 'pay_onsite', new_column_name='can_pay_onsite')
+    op.alter_column('events_version', 'tax_allow', new_column_name='is_tax_enabled')
+    op.alter_column('events_version', 'ticket_include', new_column_name='is_ticketing_enabled')
+    op.alter_column('events_version', 'has_session_speakers', new_column_name='has_sessions_speakers')
+    op.alter_column('events_version', 'sponsors_enabled', new_column_name='is_sponsors_enabled')
+    op.drop_column('events_version', 'email')
+
+    op.execute('ALTER TABLE events ALTER COLUMN show_map drop default')
+    op.execute('ALTER TABLE events ALTER COLUMN show_map TYPE BOOLEAN USING (show_map::boolean)')
+    op.execute('ALTER TABLE events ALTER COLUMN show_map SET DEFAULT TRUE')
+    op.alter_column('events', 'show_map', new_column_name='is_map_shown')
+
+    op.execute('ALTER TABLE events_version ALTER COLUMN show_map drop default')
+    op.execute('ALTER TABLE events_version ALTER COLUMN show_map TYPE BOOLEAN USING (show_map::boolean)')
+    op.execute('ALTER TABLE events_version ALTER COLUMN show_map SET DEFAULT TRUE')
+    op.alter_column('events_version', 'show_map', new_column_name='is_map_shown')
+
+    op.execute('UPDATE events SET state = LOWER(state)')
+    op.execute('UPDATE events_version SET state = LOWER(state)')
+
+
+def downgrade():
+    op.alter_column('events', 'can_pay_by_stripe', new_column_name='pay_by_stripe')
+    op.alter_column('events', 'can_pay_by_bank', new_column_name='pay_by_bank')
+    op.alter_column('events', 'can_pay_by_cheque', new_column_name='pay_by_cheque')
+    op.alter_column('events', 'can_pay_by_paypal', new_column_name='pay_by_paypal')
+    op.alter_column('events', 'can_pay_onsite', new_column_name='pay_onsite')
+    op.alter_column('events', 'is_tax_enabled', new_column_name='tax_allow')
+    op.alter_column('events', 'is_ticketing_enabled', new_column_name='ticket_include')
+    op.alter_column('events', 'has_sessions_speakers', new_column_name='has_session_speakers')
+    op.alter_column('events', 'is_sponsors_enabled', new_column_name='sponsors_enabled')
+    op.add_column('events', sa.Column('email', sa.String(), nullable=True))
+
+    op.alter_column('events_version', 'can_pay_by_stripe', new_column_name='pay_by_stripe')
+    op.alter_column('events_version', 'can_pay_by_bank', new_column_name='pay_by_bank')
+    op.alter_column('events_version', 'can_pay_by_paypal', new_column_name='pay_by_paypal')
+    op.alter_column('events_version', 'can_pay_by_cheque', new_column_name='pay_by_cheque')
+    op.alter_column('events_version', 'can_pay_onsite', new_column_name='pay_onsite')
+    op.alter_column('events_version', 'has_sessions_speakers', new_column_name='has_session_speakers')
+    op.alter_column('events_version', 'is_tax_enabled', new_column_name='tax_allow')
+    op.alter_column('events_version', 'is_ticketing_enabled', new_column_name='ticket_include')
+    op.alter_column('events_version', 'is_sponsors_enabled', new_column_name='sponsors_enabled')
+    op.add_column('events_version', sa.Column('email', sa.String(), nullable=True))
+
+    op.execute('ALTER TABLE events ALTER COLUMN is_map_shown drop default')
+    op.execute('ALTER TABLE events ALTER COLUMN is_map_shown TYPE INTEGER USING (is_map_shown::integer)')
+    op.execute('ALTER TABLE events ALTER COLUMN is_map_shown SET DEFAULT 1')
+    op.alter_column('events', 'is_map_shown', new_column_name='show_map')
+
+    op.execute('ALTER TABLE events_version ALTER COLUMN is_map_shown drop default')
+    op.execute('ALTER TABLE events_version ALTER COLUMN is_map_shown TYPE INTEGER USING (is_map_shown::integer)')
+    op.execute('ALTER TABLE events_version ALTER COLUMN is_map_shown SET DEFAULT 1')
+    op.alter_column('events_version', 'is_map_shown', new_column_name='show_map')
+
+    op.execute('UPDATE events SET state = INITCAP(state)')
+    op.execute('UPDATE events_version SET state = INITCAP(state)')


Test patch:
diff --git a/tests/unittests/object_mother.py b/tests/unittests/object_mother.py
--- a/tests/unittests/object_mother.py
+++ b/tests/unittests/object_mother.py
@@ -22,7 +22,7 @@ def get_event():
                      location_name='India',
                      topic='Travel & Outdoor',
                      sub_topic='Climbing',
-                     has_session_speakers=True,
+                     is_sessions_speakers_enabled=True,
                      type='Camp, Trip, or Retreat')
 
     @staticmethod