Reset password in Open Event API Server

The addition of reset password API in the Open Event API Server enables the user to send a forgot password request to the server so that user can reset the password. Reset Password API is a two step process. The first endpoint allows you to request a token to reset the password and this token is sent to the user via email. The second process is making a PATCH request with the token and new password to set the new password on user’s account.

Creating a Reset token

This endpoint is not JSON spec based API. A reset token is simply a hash of random bits which is stored in a specific column of user’s table.

hash_ = random.getrandbits(128)
self.reset_password = str(hash_)

Once the user completed the resetting of the password using the specific token, the old token is flushed and the new token is generated. These tokens are all one time use only.

Requesting a Token

A token can be requested on a specific endpoint  POST /v1/auth/reset-password
The token with the direct link will be sent to registered email.

link = make_frontend_url('/reset-password', {'token': user.reset_password})
send_email_with_action(user, PASSWORD_RESET,     
                       app_name=get_settings()['app_name'], link=link)

Flow with frontend

The flow is broken into 2 steps with front end is serving to the backend. The user when click on forget password will be redirected to reset password page in the front end which will call the API endpoint in the backend with an email to send the token. The email received will contain the link for the front end URL which when clicked will redirect the user to the front end page of providing the new password. The new password entered with the token will be sent to API server by the front end and reset password will complete.

Updating Password

Once clicked on the link in the email, the user will be asked to provide the new password. This password will be sent to the endpoint PATCH /v1/auth/reset-password. The body will receive the token and the new password to update. The user will be identified using the token and password is updated for the respective user.

try:
   user = User.query.filter_by(reset_password=token).one()
except NoResultFound:
   return abort(
       make_response(jsonify(error="User not found"), 404)
   )
else:
   user.password = password
   save_to_db(user)

References

  1. Understand Self-service reset password
    https://en.wikipedia.org/wiki/Self-service_password_reset
  2. Python – getrandbits()
    https://docs.python.org/2/library/random.html
Continue ReadingReset password in Open Event API Server

Post Payment Charging in Open Event API Server

Order flow in Open Event API Server follows very simple process. On successfully creating a order through API server the user receives the payment-url. API server out of the box provides support for two payment gateways, stripe and paypal.
The process followed is very simple, on creating the order you will receive the payment-url. The frontend will complete the payment through that url and on completion it will hit the specific endpoint which will confirm the payment and update the order status.

Getting the payment-url

Payment Url will be sent on successfully creating the order. There are three type of payment-modes which can be provided to Order API on creating order. The three payment modes are “free”, “stripe” and “paypal”. To get the payment-url just send the payment mode as stripe or paypal.

POST Payment

After payment processing through frontend, the charges endpoint will be called so that payment verification can be done on the server end.

POST /v1/orders/<identifier>/charge

This endpoint receives the stripe token if the payment mode is stripe else no token is required to process payment for paypal.
The response will have the order details on successful verification.

Implementation

The implementation of charging is based on the custom data layer in Orga Server. The custom layer overrides the Base data layer and provide the custom implementation to “create_object” method thus, not using Alchemy layer.

def create_object(self, data, view_kwargs):
   order = Order.query.filter_by(id=view_kwargs['id']).first()
   if order.payment_mode == 'stripe':
       if data.get('stripe') is None:
           raise UnprocessableEntity({'source': ''}, "stripe token is missing")
       success, response = TicketingManager.charge_stripe_order_payment(order, data['stripe'])
       if not success:
           raise UnprocessableEntity({'source': 'stripe_token_id'}, response)

   elif order.payment_mode == 'paypal':
       success, response = TicketingManager.charge_paypal_order_payment(order)
       if not success:
           raise UnprocessableEntity({'source': ''}, response)
   return order

With the resource class as

class ChargeList(ResourceList):
   methods = ['POST', ]
   schema = ChargeSchema

   data_layer = {
       'class': ChargesLayer,
       'session': db.session
   }

Resources

  1. Paypal Payments API
    https://developer.paypal.com/docs/api/payments/
  2. Flask-json-api custom layer docs
    http://flask-rest-jsonapi.readthedocs.io/en/latest/data_layer.html#custom-data-layer
  3. Stripe Payments API
    https://stripe.com/docs/charges
Continue ReadingPost Payment Charging in Open Event API Server

Implementing Users API to Display the Users at Admin Route in Open Event Frontend

This article will illustrate how the users are displayed and updated on the /admin/users route, their roles, user links etc. using the users API in Open Event Frontend. The primary end point of Open Event API with which we are concerned with for fetching the users is

GET /v1/users

First, we need to create a model for the user, which will have the fields corresponding to the API, so we proceed with the ember CLI command:

ember g model user

Next, we need to define the model according to the requirements. The model needs to extend the base model class. As a user can have multiple notifications, orders and  sessions etc. so we have to use ember data relationships “hasMany”. Hence, the model will have the following format.

import ModelBase from 'open-event-frontend/models/base';
import { hasMany } from 'ember-data/relationships';

export default ModelBase.extend({
  email        : attr('string'),
  password     : attr('string'),
  isVerified   : attr('boolean', { readOnly: true }),
  isSuperAdmin : attr('boolean', { readOnly: true }),
  isAdmin      : attr('boolean', { readOnly: true }),
  firstName : attr('string'),
  lastName  : attr('string')
});

The complete code for the model can be seen here

Now, we need to load the data from the API using the above model, so we will send a GET request to the API to fetch the users. This can be easily achieved using this.

return this.get('store').query('user', {'page[size]': 10 });

The above line is querying for the users from the store which is place where cache of all of the records that have been loaded by our application is there. If a route asks for a record, the store can return it immediately if it is there in the cache and we want to display only 10 users in a page so defined how many number of users has to be loaded at a time.

Now we need to filter the users based on whether they are active or they have deleted their accounts. For this purpose, we need to pass filter to the query which will tell what type of users to be loaded at once.

The next thing we need to do is to display the above data fetched from the API into an ember table. Ember table helps in allowing us to render very large data sets by only rendering the rows that are being displayed. For this, we defined a controller class which will help in letting the table know what all columns will be required to display and the attribute values they correspond in the API. We can also define the template for each column. The code for the controller class looks like this.

import Ember from 'ember';

const { Controller } = Ember;
export default Controller.extend({
  columns: [
    {
      propertyName     : 'first-name',
      title            : 'Name',
      disableSorting   : true,
      disableFiltering : true
    },
    {
      propertyName     : 'email',
      title            : 'Email',
      disableSorting   : true,
      disableFiltering : true
     },
     {
      propertyName     : 'last-accessed-at',
      title            : 'Last Accessed',
      template         : 'components/ui-table/cell/admin/users/cell-last-accessed-at',
      disableSorting   : true,
      disableFiltering : true
    }
   ]
});

In the above code, we can see a field called ‘disableSorting’ which is true if we don’t want to sort the table based on that column. Since we want the last-accessed-at column to be customized, so we have separately added a template for the column which will ensure how it will look in the column. The complete code for the other columns which are there in table apart from these can be found here.

Now to display the ember table we will write the following code.

{{events/events-table columns=columns data=model
    useNumericPagination=true
    showGlobalFilter=true
    showPageSize=true
}}

In the above piece of code, we are calling the same ember table as we used in case of events to reduce the code duplication. We are passing the columns and data in the table which remains unique to the table. Next, we are ensuring that our page shows the amount of data we’re fetching at one go, allows the filtering the table based on the columns.

The UI of the users page for the above code snippets look like this.

Fig 1: The UI of the users table under admin/users route

The entire code for implementing the users API can be seen here.

To conclude, this is how we efficiently fetched the users using the Open-Event-Orga users API, ensuring that there is no unnecessary API call to fetch the data and no code duplication using the same ember table again.

Resources:

Continue ReadingImplementing Users API to Display the Users at Admin Route in Open Event Frontend

Implementing Sessions API for the event in Open Event Frontend

This article will illustrate how the sessions are displayed and updated on the events/{event_id}/sessions route to display the sessions available for a particular event using the sessions API in Open Event Frontend. The primary end point of Open Event API with which we are concerned with for fetching the sessions is

GET /v1/sessions/{session_id}

First, we need to create a model for the sessions, which will have the fields corresponding to the API, so we proceed with the ember CLI command:

ember g model session

Next, we need to define the model according to the requirements. The model needs to extend the base model class. As a session can have multiple speakers and a session always belongs to an event, so we have to use ember data relationships “hasMany” and “belongsTo”. Hence, the model will have the following format.

import ModelBase from 'open-event-frontend/models/base';
import { belongsTo, hasMany } from 'ember-data/relationships';

export default ModelBase.extend({
  title         : attr('string'),
  subtitle      : attr('string'),

  speakers      : hasMany('speaker'),
  event         : belongsTo('event')
});

Complete code for the model can be seen here

Now, we need to load the data from the API using the above model, so we will send a GET request to the API to fetch the sessions corresponding to a particular event. This can be easily achieved using this.

return this.modelFor('events.view').query('sessions');

The above line is asking for getting the current model that is on the route events.view and query for the sessions property from that model.

Now we need to filter the sessions based on their sessions whether they have been accepted or confirmed or pending or rejected and display them on different pages. For this purpose, we need to pass filter and pages to the query which will tell what type and now of sessions to be loaded at once. Also, we need to display the speakers associated with session and event details. For this case, the above query will be formatted like this.

return this.modelFor('events.view').query('sessions', {
      include      : 'event,speakers',
    filter       : filterOptions,
      'page[size]' : 10
    });  

In the above query, the filterOptions are designed in such a way which check for what type of sessions user is querying for. The code can be found here.

The next thing we need to do is to display the above data fetched from the API into an ember table. For this, we need to have a controller class which will help in letting the table know what all columns will be required to display and the attribute values they correspond in the API. We can also define the template for each column. The code for the controller class looks like this.

export default Controller.extend({
  columns: [
    {
      propertyName   : 'state',
      title          : 'State',
      disableSorting : true,
      template       : 'components/ui-table/cell/events/view/sessions/cell-session-state'
    },
    {
      propertyName : 'title',
      title          : 'Title'
    },
    {
      propertyName    : 'speakers',
      template       : 'components/ui-table/cell/cell-speakers',
      title          : 'Speakers',
      disableSorting  : true
     }]
});

In the above code, we can see a field called ‘disableSorting’ which is true if we don’t want to sort the table based on that column. Since we want the state column to be customized, so we have separately added a template for the column which will ensure how it will look in the column. The complete code for the other columns which are there in table apart from the state, title and speakers can be found here.

Now to display the ember table we will write the following code.

{{events/events-table columns=columns data=model
    useNumericPagination=true
    showGlobalFilter=true
    showPageSize=true
}}
I

In the above piece of code, we are calling the same ember table as we used in case of events to reduce the code duplication. We are passing the columns and data in the table which remains unique to the table. Next, we are ensuring that our page shows the amount of data we’re fetching at one go, allows the filtering the table based on the columns.

The UI of the sessions page for the above code snippets look like this.

Fig 1: The UI of the session table under events/{event_id}/session route

The entire code for implementing the sessions API can be seen here.

To conclude, this is how we efficiently fetched the sessions details using the Open-Event-Orga sessions API, ensuring that there is no unnecessary API call to fetch the data and no code duplication using the same ember table again.

Resources:

Continue ReadingImplementing Sessions API for the event in Open Event Frontend

Implement Sessions API for the ‘admin/sessions’ Route in Open Event Frontend

In Open Event Frontend, under the ‘admin/sessions’ route, the admin can track the record of the sessions. The info which is shown along with the sessions is the speakers for the session, its title, submitted date, start time, end time. So to integrate the sessions API, we followed the following approach:

Firstly, we add a sessions model and establish its relationship with the speakers model since we need speaker names also:

export default ModelBase.extend({
  title         : attr('string'),
  subtitle      : attr('string'),
  startsAt      : attr('moment'),
  endsAt        : attr('moment'),
  j
  sessionType   : belongsTo('session-type'),
  microlocation : belongsTo('microlocation'),
  track         : belongsTo('track'),
  speakers      : hasMany('speaker'),
  event         : belongsTo('event'), // temporary
});

After creating the model for sessions, we do the query to get the sessions.
Since, the sessions here are classified into the sections like ‘pending’, ‘accepted’, ‘rejected’, ‘deleted’, etc, we need to filter the response returned by the query:

if (params.sessions_state === 'pending') {
      filterOptions = [
        {
          name : 'state',
          op   : 'eq',
          val  : 'pending'
        }
      ];
    } else if (params.sessions_state === 'accepted') {
      filterOptions = [
        {
          name : 'state',
          op   : 'eq',
          val  : 'accepted'
        }
      ];
    }

Above code shows how we filter the response on the server side itself. Hence we pass the filterOptions array to the query as follows:

return this.get('store').query('session', {
      include      : 'event,speakers',
      filter       : filterOptions,
      'page[size]' : 10
});

Once we get data from the query, we just pass it to our controller to implement the columns.
Once we retrieve data from the query, we just pass it to our controller to implement the columns. Since, we are using a different way to render the ember data in the column, the approach goes as follows:
In our controller we do:

export default Controller.extend({
  columns: [
    {
      propertyName   : 'event.name',
      title          : 'Event Name',
      disableSorting : true
    },
    {
      propertyName : 'title',
      title        : 'Title'
    },
    {
      propertyName   : 'speakers',
      template       : 'components/ui-table/cell/cell-speakers',
      title          : 'Speakers',
      disableSorting : true
    }
  ]
});

I have shown here only limited columns, there are others too. Here, we are mapping the propertyName to the attribute returned by the server. Also the ‘title’ indicates the column name. We can also create a custom template(as a component) if we want customization while rendering the data in the rows and columns. For example, if we want to iterate the multiple speaker names for a session, we can do:

<div class="ui list">
  {{#each record.speakers as |speaker|}}
    <div class="item">
      {{speaker.name}}
    </div>
  {{/each}}
</div>

As a result, we can make custom templates for a particular property. Another example of formatting the response is:

<span>
  {{moment-format record.startsAt 'MMMM DD, YYYY - HH:mm A'}}
  {{moment-format record.endsAt 'MMMM DD, YYYY - HH:mm A'}}
</span>

Thus, we get the following view after integration of API:

Resources:

Ember data Official guide

Blog on medium: By “Embedly” publication

Continue ReadingImplement Sessions API for the ‘admin/sessions’ Route in Open Event Frontend

Checking For Multiple Migration Heads In Open Event server

While working on Open Event server, lots of db refactor were made in the first phase. That means a multiple contributors working on the refactor of the same database.

The open event server uses SQLAlchemy as its ORM. SQLAlchemy is the Python SQL toolkit and Object Relational Mapper that gives application developers the full power and flexibility of SQL. It provides a full suite of well known enterprise-level persistence patterns, designed for efficient and high-performing database access, adapted into a simple and Pythonic domain language.

While for database migrations, the server uses Alembic. Alembic is a lightweight database migration tool for usage with the SQLAlchemy Database Toolkit for Python. Alembic provides for the creation, management, and invocation of change management scripts for a relational database, using SQLAlchemy as the underlying engine.

The issue the project collaborators faced the most when handling database migrations was when multiple PRs were merged with migration files that pointed to different heads. In such cases, Alembic would raise a multiple heads error, after the deployment has been made. There were no tests to ensure that this doesn’t happen.

The number of migration heads can be found by the following command

python manage.py db heads

 

The above command prints the identifiers of the migration heads, each on a different line. We can count the no. of lines outputted by the above command with the help of wc (word count). The result can be stored as a variable as follows:

lines=`python manage.py db heads | wc | awk '{print $1}'`

 

If the no. of lines is one, it means there is only one head and our test should pass. If the head is not one, the test should fail. Following is the test script to do that:

lines=`python manage.py db heads | wc | awk '{print $1}'`

if [ $lines -ne 1 ]
then
   exit 1
else
   exit 0
fi

 

Related links:

Continue ReadingChecking For Multiple Migration Heads In Open Event server

Adding Service Workers In Generated Event Websites In Open Event Webapp

var urlsToCache = [
 './css/bootstrap.min.css',
 './offline.html',
 './images/avatar.png'
];
self.addEventListener('install', function(event) {
 event.waitUntil(
   caches.open(CACHE_NAME).then(function(cache) {
     return cache.addAll(urlsToCache);
   })
 );
});

All the other assets are cached lazily. Only when they are requested, we fetch them from the network and store it in the local store. This way, we avoid caching a large number of assets at the install step. Caching of several files in the install step is not recommended since if any of the listed files fails to download and cache, then the service worker won’t be installed!

self.addEventListener('fetch', function(event) {
event.respondWith(caches.match(event.request).then(function(response) {
     // Cache hit - return response
     if (response) { return response; }
     // Fetch resource from internet and put into cache
     var fetchRequest = event.request.clone();
     return fetch(fetchRequest).then(function(response) {
         var responseToCache = response.clone();
         caches.open(CACHE_NAME).then(function(cache) {
           cache.put(event.request, responseToCache);
         });
         return response;
       }).catch(function(err) {
         // Return fallback page when user is offline and resource is not cached
         if (event.request.headers.get('Accept').indexOf('text/html') !== -1) {
           return caches.match('./offline.html');
         }
       });
   })
 );
});

One thing which we need to keep in mind is that the website content should not become stale. Because it might happen that the website is updated but the actual content shown is older since response to every request is being fetched from the outdated cache. We need to delete the old cache and install a new service worker when the content of the site is updated. To deal with this issue, we calculate the hash of event website folder after the site has been generated. This value of the hash is used as the cache name inside which we keep the cached resources. When the content of the event website changes, the value of the hash changes. Due to this, a new service worker (containing the new value of the hash) is installed. After the currently open pages of the website are closed, the old service worker is killed and the activate event of the new service worker is fired. During this activate event, we clear the old cache containing the outdated content!  

// Hash of the event website Folder
var CACHE_NAME = 'Mtj5WiGtMtzesubewqMtdGS9wYI=';
self.addEventListener('activate', function(event) {
 event.waitUntil(caches.keys().then(function(cacheNames) {
   return Promise.all(cacheNames.map(function(cacheName) {
     if (cacheName !== CACHE_NAME) {
       console.log('Deleting cache ' + cacheName);
       return caches.delete(cacheName);
     }
   })
   );
 }));
});

Here are many screenshots showing service workers in action

  • When the site is updated, the outdated cache contents are cleared

29312352-87f9e8da-81d2-11e7-9f05-02a557a820da.png

  • On visiting a page which has not yet been cached, its contents are placed into cache for utilization on future visits

29312487-2155e70e-81d3-11e7-85c1-004c4a475e82.png

  • On visiting the cached page again, the assets will be served from the cache directly.

29312349-87f6c222-81d2-11e7-8fb4-c54feb175ed9.png

  • The page will comfortably load even on offline connections

29312351-87f8eec6-81d2-11e7-985a-7f1ba2dba482.png

  • On requesting for a page which has not yet been cached, we show a custom fallback page which shows a message

29312350-87f821c6-81d2-11e7-90e7-30f6467b2988.pngReferences:

Continue ReadingAdding Service Workers In Generated Event Websites In Open Event Webapp

Implementing ICS/ICAL to sync calendars with the event schedule in Open Event Webapp

As an end result, we want to provide a button to the user which will export the whole data of the event schedule to an ICS file and present it to the user for download after clicking the button. The whole work regarding the feature can be seen here.

Instead of implementing the whole specification ourselves which would be much tougher and time-consuming, we looked for some good open source libraries to do a bit of heavy lifting for us. After searching exhaustively for the solution, we came across this library which seemed appropriate for the job. The heart of the library is a function which takes in an object which contains information about the session. It expects information about the start and end time, subject, description and location of the session. Here is an excerpt from the function. The whole file can be seen here

var addEvent = function (session) {
 var calendarEvent = [
   'BEGIN:VEVENT',
   'UID:' + session.uid,
   'CLASS:PUBLIC',
   'DESCRIPTION:' + session.description,
   'DTSTART;VALUE=DATETIME:' + session.begin,
   'DTEND;VALUE=DATE:' + session.stop,
   'LOCATION:' + session.location,
   'SUMMARY;LANGUAGE=en-us:' + session.subject,
   'TRANSP:TRANSPARENT',
   'END:VEVENT'
 ];
 calendarEvents.push(calendarEvent);
};

We need to call the above function for every session in the event schedule. In the schedule template file, we have the jsonData object available which contain all the information about the event. It contains a field called timeList which contains the chronological order of the different sessions taking place throughout the events. The structure of that sub-object is something like this.

[{'slug': '2017-03-20', 'times': {'caption' : '09:00-09:30', 'sessions': [{'title': 'Welcome', 'description': 'Opening of the event', 'start': '09:00', 'end': '09:30'}]}]

So, we define a function for iterating through every session in the above object and adding it to the calendar. We can use most of the attributes directly but have to modify the date and time fields of the session to an appropriate format before adding it. The specification expects time in the ISO 8601 Format. You can read more about the specification here. For eg – If the date is 2017-03-20 and the time is 09:30 then it should be written as 20170320T093000. Here is some part of the function here

function exportICS() {
 var scheduleArr = {{{json timeList}}};
 // Helper functions for converting time to ISO 8601 Format
 function removeDashFromDate(date) {
   return date.replace(/-/g, '');
 }
 function removeColonFromTime(time) {
   return time.replace(/:/g, '');
 }
 // Iteration through the object and adding every session to the calendar
 scheduleArr.forEach(function(scheduleDay) {
   var date = removeDashFromDate(scheduleDay.slug);
   scheduleDay.times.forEach(function(time) {
     time.sessions.forEach(function(session) {
       var sessObj = {};
       sessObj.begin = date + 'T' + removeColonFromTime(session.start) + '00';
       sessObj.stop = date + 'T' + removeColonFromTime(session.end) + '00';
       sessObj.subject = session.title;
       sessObj.description = session.description;
       sessObj.location = session.location;
       cal.addEvent(sessObj);
     });
   });
 });
 cal.download('calendar', 'ics', false); // Download the ics file of the calendar
}

After defining the function, we add a button for starting the download of the whole schedule of the event. On clicking, we call the function which initiates the download after all the sessions of the event have been added.

<span class="schedule-download">
 <button type="button" class="btn btn-default export-schedule"><i class="fa fa-calendar" aria-hidden="true"></i></button>
</span>

$('.export-schedule').click(function() {
 exportICS();
});

Here is the export schedule button

65203af9-3962-4ab5-9655-3250bf2253a0.png

This is the download pop-up of the ICS file of the event.

Screenshot from 2017-08-10 21-56-16.png

After importing it in the Google calendar

Screenshot from 2017-08-10 23-01-22.png

References

Continue ReadingImplementing ICS/ICAL to sync calendars with the event schedule in Open Event Webapp

Using Order Endpoints in Open Event API Server

The main feature i.e., Ordering API is added into API server. These endpoints provide the ability to work with the ordering system. This API is not simple like other as it checks for the discount codes and various other things as well.
The process in layman terms is very simple, first, a user must be registered or added as an attendee into Server without any order_id associated and then the attendee details will be sent to API server as a relationship.

Things needed to take care:

  1. Validating the discount code and ensure it is not exhausted
  2. Calculating the total amount on the server side by applying coupon
  3. Do not calculate amount if the user is the event admin
  4. Do not use coupon if user is event admin
  5. Handling payment modes and generating payment links
  6. Ensure that default status is always pending, unless the user is event admin

Creating Order

    • Prerequisite
      Before initiating the order, attendee records needs to be created associated with the event. These records will not have any order_id associated with them initially. The Order API will add the relationships.
    • Required Body
      Order API requires you to send event relationship and attendee records to create order_tickets
    • Permissions
      Only organizers can provide custom amount and status. Others users will get their status as pending and amount will be recalculated in server. The response will reflect the calculated amount and updated status.
      Also to initiate any order, user must be logged in. Guest can not create any order
    • Payment Modes
      There are three payment modes, free, stripe and paypal. If payment_mode is not provided then API will consider it as “free”.
    • Discount Codes
      Discount code can be sent as a relationship to the API. The Server will validate the code and will act accordingly.

Validating Discount Codes

Discount codes are checked to ensure they are valid, first check ensures that the user is not co-organizer

# Apply discount only if the user is not event admin
if data.get('discount') and not has_access('is_coorganizer', event_id=data['event']):

Second, check ensures that the discount code is active

if not discount_code.is_active:
  raise UnprocessableEntity({'source': 'discount_code_id'}, "Inactive Discount Code")

The third, Check ensures its validity is not expired

if not (valid_from <= now <= valid_till):
  raise UnprocessableEntity({'source': 'discount_code_id'}, "Inactive Discount Code")

Fourth Check ensure that the quantity is not exhausted

if not TicketingManager.match_discount_quantity(discount_code, data['ticket_holders']):
  raise UnprocessableEntity({'source': 'discount_code_id'}, 'Discount Usage Exceeded')

Lastly, the fifth check ensures that event id matches with given discount associated event

if discount_code.event.id != data['event'] and discount_code.user_for == TICKET:
  raise UnprocessableEntity({'source': 'discount_code_id'}, "Invalid Discount Code")

Calculating Order Amount

The next important thing is to recalculate the order amount and it will calculated only if user is not the event admin

if not has_access('is_coorganizer', **view_kwargs):
  TicketingManager.calculate_update_amount(order)

API Response

The API response apart from general fields will provide you the payment-url depending upon the payment mode you selected.

  • Stripe : will give payment-url as stripe
  • Paypal: will provide the payment completing url in payment-url

This all explains the flow and requirements to create an order. Order API consists of many more things related with TIcketing Manager which works to create the payment url and apply discount count as well as calculate the total order amount.

Resources

  1. Stripe Payments API Docs
    https://stripe.com/docs/api
  2. Paypal Payments API docs
    https://developer.paypal.com/docs/api/
  3. Paypal Sandbox docs
    https://developer.paypal.com/docs/classic/lifecycle/ug_sandbox/

 

Continue ReadingUsing Order Endpoints in Open Event API Server

Adding Sticky Headers for Grouping Sponsors List in Open Event Android App

The Open Event Android project has a fragment for showing sponsors of the event. Each Sponsor model has a name, url, type and level. The SponsorsFragment shows list according to type and level. Each sponsor list item has sponsor type TextView. There can be more than one sponsors with the same type. So instead of showing type in the Sponsor item we can add Sticky header showing type at the top which will group the sponsors with the same type and also gives the great UI. In this post I explain how to add the Sticky headers in the RecyclerView using StickyHeadersRecyclerView library.

1. Add dependency

In order to use Sticky Headers in your app add following dependencies in your app module’s build.gradle file.

dependencies {
	compile 'com.timehop.stickyheadersrecyclerview:library:0.4.3'
}

2. Create layout for header

Create recycler_view_header.xml file for the header. It will contain LinearLayout and simple TextView which will show Sponsor type.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <TextView
        android:id="@+id/recyclerview_view_header"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="@dimen/padding_medium" />

</LinearLayout>

Here you can modify layout according to your need.

3.  Implement StickyRecyclerHeadersAdapter

Now implement StickyRecyclerHeadersAdapter in the List Adapter. Override getHeaderId(), onCreateHeaderViewHolder(), onBindHeaderViewHolder
() methods of the StickyRecyclerHeadersAdapter.

public class SponsorsListAdapter extends BaseRVAdapter<Sponsor, SponsorViewHolder> implements StickyRecyclerHeadersAdapter {
    ...

    @Override
    public long getHeaderId(int position) {...}

    @Override
    public RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent) {...}

    @Override
    public void onBindHeaderViewHolder(RecyclerView.ViewHolder holder, int position) {...}
}

 

The getHeaderId() method is used to give an id to the header. It is the main part of the implementation here all the sponsors with the same type should return the same id. In our case we are returning sponsor level because all the sponsor types have corresponding levels.

String level = getItem(position).getLevel();
return Long.valueOf(level);

 

The onCreateHeaderViewHolder() returns Recycler ViewHolder for the header. Here we will use in the inflate() method of  LayoutInflater to get View object of recycler_view_header.xml file. Then return new RecyclerView.ViewHolder object using View object.

View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.recycler_view_header, parent, false);
return new RecyclerView.ViewHolder(view) {};

 

The onBindHeaderViewHolder() binds the sponsor to HeaderViewHolder. In this method we sets the sponsor type string to the TextView we have created in the recycler_view_header.xml file.

TextView textView = (TextView) holder.itemView.findViewById(R.id.recyclerview_view_header);
textView.setGravity(Gravity.CENTER_HORIZONTAL);

String sponsorType = getItem(position).getType();
if (!Utils.isEmpty(sponsorType))  
   textView.setText(sponsorType.toUpperCase());

Here you can also modify TextView according to your need. We are centering text using setGravity() method.

4.  Setup RecyclerView

Now create RecyclerView and set adapter using setAdapter() method. Also as we want the linear list of sponsors so set the LinearLayoutManager using setLayoutManager() method.

SponsorsListAdapter sponsorsListAdapter = new SponsorsListAdapter(getContext(), sponsors);
sponsorsRecyclerView.setAdapter(sponsorsListAdapter);
sponsorsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

 

Create StickyRecyclerHeadersDecoration object and add it in the RecyclerView using addItemDecoration() method.

final StickyRecyclerHeadersDecoration headersDecoration = new StickyRecyclerHeadersDecoration(sponsorsListAdapter);

sponsorsRecyclerView.addItemDecoration(headersDecoration);
sponsorsListAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver(){
    @Override
    public void onChanged {
            headersDecoration.invalidateHeaders();
    }
});

Now add AdapterDataObserver using registerAdapterDataObserver() method. The onChanged() method in this observer is called whenever dataset changes. So in this method invalidate headers using invalidateHeaders() method of HeaderDecoration.

Now we are all set. Run the app it will look like this.

Conclusion

Sticky headers in the App gives great UI and UX. You can also add a click listener to the headers. To know more about Sticky Headers follow the links given below.

Continue ReadingAdding Sticky Headers for Grouping Sponsors List in Open Event Android App