Nested Routes in Ember JS in Open Event Frontend

When defining routes in ember, there is one very important thing to keep in mind. We should only nest our routes when our UI is nested. Sometimes it is necessary to display a template inside another template and here we use nested routes.

Let’s now learn how to create nested routes. We can take a look at the notifications page in our Open Event Frontend for better understanding.

Here the part in red is common to all the sub-routes of notification, thus one method of achieving this could be copy-pasting the same code in all the sub-routes. Obviously this is not the correct method and it increases the code redundancy.

The correct method here is, using nested routes.

ember generate route notifications
ember generate route notifications/index
ember generate route notifications/all

Here notification is the parent route and index, all are it’s nested or sub-routes. It can be seen in router.js in the given form

this.route(‘notifications’, function() {
   this.route(‘all’);
});

But wait we see no route for notifications/index…. did something go wrong ?

 

No. At every level of nesting, ember automatically creates a route for the path ‘/’ named index. So the above code snippet in router.js is equivalent to

this.route(‘notifications’, function() {

   this.route(‘index’,{ path: ‘/’});
   this.route(‘all’);
});

Now let’s have a look at our parent route ie /notifications. As stated above only the part in red has to be present here, So we have added semantic-ui’s header, buttons and used link-to helper to link them to respective route. An interesting thing to note here is that ‘Mark all read’ is not present in both its sub routes, so we need to check the route before displaying this button. We have checked the current route using session.currentRouteName and only when the current route is not notifications/all we display the button.

<h1 class=“ui header”>{{t ‘Notifications’}}</h1>
<div class=”ui divider”></div>
<div class=“row”>
 {{#link-to ‘notifications’}}
   <button class=“ui button”>{{t ‘Unread’}}</button>
 {{/link-to}}
 {{#link-to ‘notifications.all’}}
   <button class=“ui button”>{{t ‘All’}}</button>
 {{/link-to}}
 {{#if (not-includes session.currentRouteName ‘notifications.all’)}}
   <button class=“ui button right floated”>{{t ‘Mark all read’}}</button>
 {{/if}}
</div>
{{outlet}}

We have added the {{outlet}} helper in our notifications.hbs where we want our nested templates to be displayed. For understanding we can imagine this as the whole code of notifications/all is being copied into this outlet helper i.e notifications/all.hbs is rendered into the {{outlet}} of notifications.hbs

Let’s now have a look at our sub-route i.e notifications/all.hbs. We now know that this will be rendered in the red part stated below

We have added Semantic-Ui’s segments class for every notification. A blue colour has been added to the segments if the notification is not read. We have used Semantic-Ui’s grid class to structure the content inside each notifications. We are getting date object from js and to convert it into human readable format we have used moment like {{moment-from-now notification.createdAt}}

{{#each model as |notification|}}
 <div class=“ui segment {{unless notification.isRead ‘blue’}}”>
   <div class=“ui grid”>
     <div class=“row”>
       <div class=“eight wide column”>
         <h4 class=“ui header”>{{notification.title}}</h4>
         <p>{{notification.description}}</p>
       </div>
       <div class=“eight wide column right aligned”>
         <i class=“checkmark box icon”></i>
       </div>
     </div>
     <div class=“row”>
       <div class=“ten wide column”>
         <button class=“ui blue button”>{{t ‘View Session’}}</button>
       </div>
       <div class=“six wide column right aligned”>
         <p>{{moment-from-now notification.createdAt}}</p>
       </div>
     </div>
   </div>
 </div>
{{/each}}

We have reused notifications/all.hbs for index route by explicitly defining templateName: ‘notifications/all’, in notifications/index.js.

Additional Resources

Continue ReadingNested Routes in Ember JS in Open Event Frontend

Customizing Chromium for the Meilix Generator

Imagine if you are able to vanish that star icon of the bookmark which is on the extreme right of address bar. Disable auto-fill option, disable some particular extension to get install and many more things. And you could even distribute it to any friend to get the same setting within just copy and paste. We are working on such features for the FOSSASIA Meilix generator for Chromium.

Chromium is one of the most popular browsers. But had you ever thought how grateful it would be if you are able to customize your chromium to a larger extent? Sometimes it feels that few features are there which we merely used.

So here it is the way to tailor cut the specification of the browser and you can even give it to your friend to try out the feature. It just needs to copy and paste a file for your friend. Not forget to mention from where do I get this file: fossasia/meilix

How can you do that?

This gist is a .json file which has to be copied in the etc/chromium-browser/policies/managed with the name chrome.json. That’s it.

Chrome.json
It’s a policy template of Linux for the browser Chromium. This file contains different policies and they are commented. Uncomment the required values and set your desired values.

Format of the policy of the JSON file.

Each policy is well-structured so that a person can easily understand and change its values.

Let’s take an example:

1 // Enable Bookmark Bar
2 //-------------------------------------------------------------------------
3 // Enables the bookmark bar on Google Chrome.  If you enable this setting,
4 // Google Chrome will show a bookmark bar.  If you disable this setting, users
5 // will never see the bookmark bar.  If you enable or disable this setting,
6 // users cannot change or override it in Google Chrome.  If this setting is
7 // left not set the user can decide to use this function or not.
8 //"BookmarkBarEnabled": true,

This is an example of controlling of bookmark bar which is mention on line 1
Line 2 is left intentionally for proper formatting
Line 3-7 explains the purpose of the policy, it explains itself quite briefly
Line 8 is the by default set option, to alter it, uncomment it and reverse the values.

This same way is being followed throughout. There are many other options which act as a boon.

Edit the file and share it with your friends and ask them to copy it in the same location and then they can also get the benefit of the feature.

Continue ReadingCustomizing Chromium for the Meilix Generator

Understanding the working of SUSI Hardware

Susi on Hardware is the latest addition to full suite of SUSI Apps. Being a hardware project, one might feel like it is too much complex, however it is not the case.

The solution is being primary built on a Raspberry Pi which, however small it may be, is a computer. Most things you expect to work on a normal computer, work on Raspberry Pi as well with a few advantages being its small size and General Purpose I/O access. But it comes with caveats of an ARM CPU, which may not support all applications which are mainly targeted for x86.
There are a few other development boards from Intel as well, which use x86/x64 architecture.

While working on the project, I did not wanted to make it too generic for a board or set of Hardware, thus all components used were targeted to be cross-platform.

Components that make Susi Hardware

SUSI Server

SUSI Server is the foremost important thing in any SUSI Project. SUSI Server handles all the queries by user which can be supplied using REST API and supplies answer in a nice format for clients. It also provides AAA: Authentication, Authorization and Accounting support for managing user accounts across platforms.

Github Repository: https://github.com/fossasia/susi_server

Susi Python Library

Susi Python Library was developed along with Susi Hardware project. It can work independent of Hardware Project and can be included in any Python Project for Susi Intelligence. It provides easy access to Susi Server REST API through easy python methods.
Github Repository: https://github.com/fossasia/susi_api_wrapper

Python Speech Recognition Library

The best advantage of using Python is that in most cases , you do not need to re-invent the wheel, some already has done the work for you. Python Speech Recognition library support for speech recognition through microphone and by a voice sample. It supports a number of Speech API providers like Google Speech API. Wit.AI, IBM Watson Speech-To-Text and a lot more.
This provides free to choose any of the speech recognition providers. For now, we are using Google Speech API and IBM Watson Speech API.


Pypi Package: https://pypi.python.org/pypi/SpeechRecognition/Github Repository: https://github.com/Uberi/speech_recognition

PocketSphinx for Hotword Detection

CMU PocketSphinx is an open-source offline speech recognition library. We have used PocketSphinx to enable hotword detection to Susi Hardware so you can interact with Susi handsfree.
More information on its working can be found in my other blog post.

Github Repository: https://github.com/cmusphinx/pocketsphinx

Flite Speech Synthesis System

CMU Flite (Festival-Lite) is a small sized , fast and open source speech synthesis engine developed by Carnegie Mellon University .
More information of integration and usage in Susi can be found in my other blog post

Project Website: http://www.festvox.org/flite/

The whole working of all these components together can be explained using the Diagram below.

Continue ReadingUnderstanding the working of SUSI Hardware

Hotword Detection for SUSI Android with CMUsphinx

Being an AI for conversational bots, Hotword detection of SUSI is the top priority to the community. Another requirement was that there should be an option for an offline hotword detection. So, I was searching for an API that has all these capabilities. Sphinx by CMU was the obvious choice. It provides robust mechanism for hotword detection.

What is CMUsphinx?

CMUsphinx is open source and leading speech recognition toolkit. CMUsphinx has different modules for different tasks it needs to perform. Our requirement for SUSI is, that is needs to be lightweight, So we are using Pocketsphinx. Before going into integration let us discuss about basics of speech recognition.

Let us dive into coding and integrating Susi with pocketsphinx.

Building Pocketsphinx .AAR file

Git clone the sphinxbase, pocketsphinx and pocketsphinx-android and put them in the same folder. By following commands below.

git clone http://github.com/cmupshinx/sphinxbase
git clone http://github.com/cmupshinx/pocketsphinx
git clone http://github.com/cmupshinx/pocketsphinx-android

Then import pocketsphinx Android into Android studio. Run the project. .aar files pocketsphinx-android-5prealpha-debug.aar & pocketsphinx-android-5prealpha-release.aar  will be created in the build/outputs/aar.

Integrating Susi with Pocketsphinx

In Android Studio you need to the above generated AAR into your project. Just go to File > New > New module and choose Import .JAR/.AAR Package. After this, We need to change permissions of project. Add the following permissions in AndroidManifest.xml.

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

Import the following functions into your main activity.

import edu.cmu.pocketsphinx.Assets;
import edu.cmu.pocketsphinx.Hypothesis;
import edu.cmu.pocketsphinx.RecognitionListener;
import edu.cmu.pocketsphinx.SpeechRecognizer;
import edu.cmu.pocketsphinx.SpeechRecognizerSetup;

Next we need to sync the assets we get from .aar file in to our project. Edit app/build.gradle build file to run assets.xml. We do it by adding following code to build.gradle.

ant.importBuild 'assets.xml'
preBuild.dependsOn(list, checksum)
clean.dependsOn(clean_assets)

Now all the import and sync errors of gradle must disappear and you should be good to go. You can start your recognizer by adding this code to your activity.

recognizer = defaultSetup()
        .setAcousticModel(new File(assetsDir, "en-us-ptm"))
        .setDictionary(new File(assetsDir, 
"cmudict-en-us.dict"))
        .getRecognizer();
recognizer.addListener(this);

Decoder model is lengthy process that contains many operations, so it’s recommended to run in inside async task. These are commands for decoder to run. These commands essentially do acoustic and language modelling of speech.

// Create keyword-activation search.
recognizer.addKeyphraseSearch(KWS_SEARCH, KEYPHRASE);

// Create grammar-based searches.
File menuGrammar = new File(assetsDir, "menu.gram");
recognizer.addGrammarSearch(MENU_SEARCH, menuGrammar);

// Next search for digits
File digitsGrammar = new File(assetsDir, "digits.gram");
recognizer.addGrammarSearch(DIGITS_SEARCH, digitsGrammar);

// Create language model search.
File languageModel = new File(assetsDir, "weather.dmp");
recognizer.addNgramSearch(FORECAST_SEARCH, languageModel);

Speech recognition will end at onEndOfSpeech callback of the recognizer listener.  We can call recognizer.stop or recognizer.cancel(). Cancel will cancel the recognition, stop will cause the final result be passed you in onResult callback. During the recognition, you will get partial results in onPartialResult callback.

Now we have integrated Pocketsphinx with SUSI.AI in Android.

Continue ReadingHotword Detection for SUSI Android with CMUsphinx

Integration Testing of an Ember Component in Open Event Frontend

Open Event Frontend uses ember components which are reused several times throughout the project, making these components is one thing but we should also ensure they work as expected. To ensure this we need integration tests.How to write an integration test for event-map component? Three files are generated when we generate our event-map component from the command shell. They are namely:

event-map.js

event-map.hbs

event-map-test.js

We are familiar with the above three files as:

  1. event-map.js helps us to provide properties to our event-map.hbs
  2. event-map.hbs is where we define the structure of our component.
  3. event-map-test.js is where we define integration test for our component. Also, which is the current topic for discussion.

Testing

In order to test the rendering of the component, we define an integration test for that component. In integration tests we don’t have to launch our whole application and navigate to the location where our component is present. This makes it ideal for testing components. Have a look at the following sample integration test file.

import { test } from 'ember-qunit';
import moduleForComponent from 'open-event-frontend/tests/helpers/component-helper';
import hbs from 'htmlbars-inline-precompile';

moduleForComponent('public/event-map', 'Integration | Component | public/event map');

let event = Object.create({ latitude: 37.7833, longitude: -122.4167, locationName: 'Sample event location address' });

test('it renders', function(assert) {
  this.set('event', event);
  this.render(hbs `{{public/event-map event=event}}`);
  assert.equal(this.$('.address p').text(), 'Sample event location address');
});

So let’s break down this code line by line-

In the first line we have imported test from ‘ember-qunit’ (default unit testing helper suite     for Ember) which contains all the required test functions. For example, here we are using test function to check to check the rendering of our component. We can use test function multiple times to check multiple components.

Next, we are importing moduleForComponent from ‘open-event-frontend/tests/helpers/component-helper’ helper which helps in finding the component by its name.

Next,  we are importing hbs from ‘htmlbars-inline-precompile’ which basically imports precompile HTMLBars template strings within the tests via ES6 tagged template strings.

The moduleForComponent helper will find the component by name (event-map) and its template. The component we are testing here is a map-related. Therefore, to test this, we need to pass a dummy object consisting of latitude, longitude and locationName. The object data must render correctly in our app.

Inside our test function, this.set(‘event’, event) assigns a variable ( here event ) to our test context.

this.render (hbs `{{public/event-map event=event}}`) lets us create a new instance of the component by declaring the component in template syntax, as we would in our application.

assert.equal(this.$(‘.address p’).text(), ‘Sample event location address’) is basically a check between actual and expected arguments.

For a simple component like a table or a basic UI only component, it is not necessary to pass any object to the component. Only the rendered component can be tested as well. Apart from checking the component rendering we can perform tests on it by using test functions as described above. After writing the integration tests for components, simply run ember test –server on the terminal to see if all the tests have passed or not.      

Find out more about Integration testing in ember –

Ember guides, EmberIgniter

Continue ReadingIntegration Testing of an Ember Component in Open Event Frontend

Using Dynamic segments to Reduce Code Redundancy of Recurring HTML in Open Event ember Frontend

While developing web apps, at times we require the same HTML for different pages in our app. This leads to redundancy and low code-reusability. This can be well managed in ember.js by using dynamic segments in our routes.

In Open Event Front-end we have a route named /sessions where we want to show the details of the event’s sessions and we want to categorize the sessions in all, pending, accepted, confirmed and rejected sessions hence want to create the following subroutes under it.

events/<event-id>/sessions
events/<event-id>/sessions/pending
events/<event-id>/sessions/accepted
events/<event-id>/sessions/confirmed
events/<event-id>/sessions/rejected

All of these subroutes show different data based on the routes in a table with exactly same fields. So if we use dynamic segments, we can decrease code redundancy and increase code reusability. So let us see how to add these subroutes as dynamic segments.

Firstly, we have to add a dynamic part (pending, accepted, confirmed, rejected) under our URL /sessions . For this, edit the following code snippet to the router.js file. In place of list write the name of route handler which handles our subroutes and in place of session_status write any identifier you want as session_status is the dynamic part which changes according to subroutes. In our case it will be pending, accepted, confirmed or rejected.

     this.route('sessions', function() {
        this.route('list', { path: '/:session_status' });
      });

To display all the sessions in our /sessions route, we have to edit index route handler and return data from model hook. Now when we hit  /sessions end point, the template which we are using redundantly, i.e. list.hbs, gets data from a model hook of this route handler.

Next we need to define a model hook in our list.js file which returns data for the dynamic routes. In list.js, we would want to change the title of our page according to the dynamic segments. These dynamic segments are available under model hook in this route under param parameter. We are using this.set which sets the provided key or path to the value. Make this available in titleToken function and by applying simple switch case, we can change the title dynamically.

Till now, we could access our dynamic segments by manually changing the URL. Let’s add a link to do this transition automatically for us. For this, we edit our session.hbs file and provide links using a linkto helper. One thing to take care here is that we should pass the dynamic segments along with the link-to helper.

{{#link-to 'events.view.sessions.list' 'pending' class='item'}}

Here, pending is the dynamic segment which we are passing to our route handler. Similarly, we can make the links for all our dynamic segments. Also in this template, we should provide outlets for the common template i.e list.hbs which will be reused by other dynamic subroutes. And finally, we define our reusable template in list.hbs file.

Now when we hit on different links we are redirected to different routes which are using different data but same templates. Also, we can see this transition in our URL and title.

To know more about dynamic segments refer to Ember guide.

Continue ReadingUsing Dynamic segments to Reduce Code Redundancy of Recurring HTML in Open Event ember Frontend

Uploading Images via APIs in the Open Event Server

APIs help us to send and receive data in some particular data format that can then be used individually or integrated with a frontend UI. In our case, the entire API server is used to manage all the requests from the frontend and send back the necessary response. Usually, the application is to send simple form data which is then stored into the backend database and a valid jsonapi response is shown. However other than normal text, url, datetime data one very important data is media files, in our case event images, user images, slides, etc. In this blog, we will particularly deal with how we can upload images in the server using API.

Sending Data

Firstly, we need to decide how do we send the data in the post request of the API. So we are sending a base64 encoded string representing the image along with the image extension appended to it, for example, data:image/png;base64,iVBORw0KGgoAAAANS. This is a widely used format for showing images over the web. So when we send a POST request we send a json encoded body like:

{
    "data": "data:image/png;base64,iVBORw0KGgoAAAANS"
}

Converting Base64 Data to Image

There are 2 parts of the data in the string that we receive. The first part basically tells us the format of the image(.gif in this case) and string encoding(base64 in this case), the second part gives us the encoded string. So, from the first part, we extract the file extension for the image file to be created. We use uuid.uuid4() for a random filename.

filename = '{}.{}'.format(str(uuid.uuid4()).data.split(";")[0].split("/")[1])

Now to write the base64 encoded string as an image file, we first need to get only the encoded string part from the data and then decode it. We use string decode function of python for the decoding purpose. Then we write the data to the image file and save it.

file.write(data.split(",")[1].decode("base64")

API Response

Finally using whatever logic you are using for serving your static files, you generate the static file path for the image saved. And then create another json encoded response which returns you the url for the saved image in the server.

{
    "url": "https://xyz.storage.com/asd/fgh/hjk/1233456.png"
}

And there you have your image uploaded and served.

Continue ReadingUploading Images via APIs in the Open Event Server

Decorators in Open Event API Server

One of the interesting features of Python is the decorator. Decorators dynamically alter the functionality of a function, method, or class without having to directly use subclasses or change the source code of the function being decorated.

Open Event API Server makes use of decorator in various ways. The ability to wrap a function and run the decorator(s) before executing that function solves various purpose in Python. Earlier before decoupling of Orga Server into API Server and Frontend, decorators were being used for routes, permissions, validations and more.

Now, The API Server mainly uses decorators for:

  • Permissions
  • Filtering on the basis of view_kwargs or injecting something into view_kwargs
  • Validations

We will discuss here first two because validations are simple and we are using them out of the box from marshmall-api

The second one is custom implementation made to ensure no separate generic helpers are called which can add additional database queries and call overheads in some scenarios.

Permissions Using Decorators

Flask-rest-jsonapi provides an easy way to add decorators to Resources. This is as easy as defining this into Resource class

  1. decorators = (some_decorator, )

On working to event role decorators to use here, I need to follow only these 3 rules

  • If the user is admin or super admin, he/she has full access to all event roles
  • Then check the user’s role for the given event
  • Returns the requested resource’s view if authorized unless returns Forbidden Error response.

One of the examples is:

def is_organizer(view, view_args, view_kwargs, *args, **kwargs):
  user = current_identity
 
  if user.is_staff:
      return view(*view_args, **view_kwargs)
 
  if not user.is_organizer(kwargs['event_id']):
      return ForbiddenError({'source': ''}, 'Organizer access is required').respond()
 
  return view(*view_args, **view_kwargs)

From above example, it is clear that it is following those three guidelines

Filtering on the basis of view_kwargs or injecting something into view_kwargs

This is the main point to discuss, starting from a simple scenario where we have to show different events list for different users. Before decoupling API server, we had two different routes, one served the public events listing on the basis of event identifier and other to show events to the event admins and managers, listing only their own events to their panel.

In API server there are no two different routes for this. We manage this with a single route and served both cases using the decorator. This below is the magic decorator function for this purpose

def accessible_role_based_events(view, view_args, view_kwargs, *args, **kwargs):
  if 'POST' in request.method or 'withRole' in request.args:
      _jwt_required(app.config['JWT_DEFAULT_REALM'])
      user = current_identity
      if 'GET' in request.method and user.is_staff:
          return view(*view_args, **view_kwargs)
      view_kwargs['user_id'] = user.id
  return view(*view_args, **view_kwargs)

It works simply by looking for ‘withRole’ in requests and make a decision to include user_idinto kwargs as per these rules

  1. If the request is POST then it has to be associated with some user so add the user_id
  2. If the request is GET and ‘withRole’ GET parameter is present in URL then yes add the user_id. This way user is asking to list the events in which I have some admin or manager role
  3. If the request is GET and ‘withRole’ is defined but the logged in user is admin or super_adminthen there is no need add user_id since staff can see all events in admin panel
  4. The last one is GET and no ‘withRole’ parameter is defined therefore ignores and continues the same request to list all events.

The next work is of query method of EventList Resource

if view_kwargs.get('user_id'):
          if 'GET' in request.method:
              query_ = query_.join(Event.roles).filter_by(user_id=view_kwargs['user_id']) \
                  .join(UsersEventsRoles.role).filter(Role.name != ATTENDEE)

This query joins the UsersEventsRoles model whenever user_id is defined. Thus giving role-based events only.

The next interesting part is the Implementation of permission manager to ensure permission decorators doesn’t break at any point. We will see it in next post.

References:

https://wiki.python.org/moin/PythonDecorators

Continue ReadingDecorators in Open Event API Server

Open Event Server: Dealing with environment variables

FOSSASIA‘s Open Event Server uses some environment variables to maintain different configuration for different deployment. However one con of it was, for local development, developers have to run many export commands.

It all started with the “It is not convenient, we humans are lazy.” phrase. A recent suggestion was to read this environment variables with files rather than running commands every time.
This few example environment variable commands were:

export DATABASE_URL=postgresql://open_event_user:test@127.0.0.1:5432/test

and

export INTEGRATE_SOCKETIO=false

 

Open Event Server now uses .env file and envparse to handle this.

The changes were:

  1. We store the variables in .env file, let it get ignored by git, and
  2. Use envparse to parse the file.

What is envpase:
       envparse is a simple utility to parse environment variables. It aims to eliminate duplicated, often inconsistent parsing code and instead provide a single, easy-to-use wrapper.

So instead of reading the string from the command line everytime:

export DATABASE_URL=postgresql://open_event_user:password@127.0.0.1:5432/open_event_db

We read it from the file:

To read from command line we had:

SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', None)

Which now was parsed using envparse as:

SQLALCHEMY_DATABASE_URI =  env('DATABASE_URL', default=None)

In the above example, DATABASE_URL is a string and we store this string in SQLALCHEMY, setting the default by the default parameter(None in this case).

The type of environment variable is originally string, we can simply use env to parse these strings. For simplicity and understanding we can also use env.str. For different types, we can simply type cast them like env.bool (for boolean), env.int (for integer) etc. One good advantage of this envparse feature is the direct use of these type cast rather than checking for strings (i.e. reading the string from command line).

For example, earlier we had something like this:

socketio_integration = os.environ.get('INTEGRATE_SOCKETIO’)
if socketio_integration == true’:
INTEGRATE_SOCKETIO =True

Using envparse, we can have something like this:

INTEGRATE_SOCKETIO = env.bool('INTEGRATE_SOCKETIO’, default=False)

This helps in parsing as boolean instead of string. So, if we have INTEGRATE_SOCKETIO=true in .env file, we can write INTEGRATE_SOCKETIO = env.bool(‘INTEGRATE_SOCKETIO’, default=False). This automatically converts the true to boolean True in python.

In order to use envparse we install envparse with:

$ pip install envparse


In the case of
Open Event Server project, you only have to install the requirements.txt and copy the .env.example already mentioned in installation doc.

This is how we finally remove the headache of writing big exports again and again and fairly helps beginners to start off with Open Event Server  project much more easily and quickly.

Continue ReadingOpen Event Server: Dealing with environment variables

Permission Dependent Schema for Admin Settings in Open Event Server

For implementing the next version of the API in Open Event, the schema is a very important thing. It tells you exactly what all information you need to send in the body and how the response will look. In flask-rest-jsonapi, we usually mention a schema for an API which is then used for validating requests and sending response. Using decorators, we restrict who all can create, edit or get responses from a particular API endpoint. However, a scenario may so arise that you need to show data to users at different permissions level, but the amount of data shown significantly varies with the permission.

For example, for the settings API in our case. There are few informations like the app name, app tagline that we want to be available to users at all permission levels. However, informations such as aws secret key, or mailing secret keys or any other secret key, we want that to be available only to the admin and super admin. And the responses should be such that users at different permission level should feel that whatever information shown to them is complete and not missing.

So, what we do is we create different schemas, in our case 2 different schemas. Depending on the permission of the user, we show them a particular schema. In our case, the two schemas are SettingSchemaAdmin and SettingSchemaNonAdmin. In SettingSchemaAdmin, we have all the attributes or fields that are present and is accessible to the Admin and Super Admin. In the SettingSchemaNonAdmin however, we have only those fields and attributes that we want to show to all non admin users.

from flask_jwt import current_identity
 
class SettingDetail(ResourceDetail):
    """
    setting detail by id
    """
 
    def before_get(self, args, kwargs):
        kwargs['id'] = 1
        if current_identity.is_admin or current_identity.is_super_admin:
            self.schema = SettingSchemaAdmin
        else:
            self.schema = SettingSchemaNonAdmin

 

The above code helps us achieve this. If you have read previous blogs about the API server, you would already know that we are using JWT for authenticating our users. In this code, we are importing current_identity from flask_jwt. Current_identity, returns us an object of the User type which has properties such as is_admin, is_super_admin, etc. to help us identify the permission level of that user.
Using this object, we check whether the user who is making the request via jwt authentication is an admin or super admin, or just a normal registered user.

        if current_identity.is_admin or current_identity.is_super_admin:
            self.schema = SettingSchemaAdmin
        else:
            self.schema = SettingSchemaNonAdmin

 

So, if the current user sending the request is an admin, then we set the schema for the Resource manager class of the flask-rest-jsonapi as SettingSchemaAdmin, which we have already declared before containing all the fields, else, we set it as SettingSchemaNonAdmin which has limited number of attributes.

Continue ReadingPermission Dependent Schema for Admin Settings in Open Event Server