Implementing Copyright API in Open Event Frontend

This article illustrates how the copyright details have been displayed in the Open Event Frontend project using the Open Event Orga API. The API endpoints which will be mainly focussing on for fetching the copyright details are: GET /v1/event-copyright/{event_copyright_id} The events have copyrights which give the creator of the event exclusive rights for its use and distribution. In the Open Event application, the copyright details can be seen on the public event page. The public event page contains the events details like description, venue, tickets, speakers, sponsors along with the copyright details and these details are present on the public/index route in the application. Apart from index route, we have multiple subroutes to display the detailed information of speakers, sessions and schedule. The one thing which remains common to all the public event pages is the copyright information. Most of the time the copyright details are event specific so they are nested within the event model so if we want to display them we need to fetch the event model first. The code to fetch the event model looks like this: model(params) { return this.store.findRecord('event', params.event_id, { include: 'social-links, event-copyright' }); } If we try to comprehend the code then we can see that ‘event-copyright’ details are included inside the model. The reason behind this is the fact that the copyright information is not specific to a particular route and is displayed on the all the public event pages. After fetching the copyright details the next step we need to perform is to display them on the event’s index page. The code to display the copyright details looks like this: {{#if model.event.copyright}} <div class="copyright"> {{public/copyright-item copyright=model.event.copyright}} </div> {{/if}} In the first line, we have an if conditional statement which verifies whether the copyright data exists or not. If the data does not exist then the copyright class will not be visible on the page and if the model is not empty then it will be displayed with the help of model.event.copyright which is responsible for displaying the fetched data on the page. If we see in the third line, we have called an another template ‘copyright-item’ which is responsible for how the data will look or in simpler words the UI of the copyright data. The code which determines UI of the copyright details looks like this: <img src="{{copyright.logoUrl}}" class="copyright-image" alt="{{copyright.licence}}"> <br> <div class='copyright text'> <p> {{t 'This event is licenced under'}} <a href="{{copyright.licenceUrl}}"> {{copyright.licence}} </a>. </p> </div> In the first line of code, we are providing the src to the image which is stored in ‘logoUrl’ variable of the copyright object. If we hover the image we can see the copyright license which is stored in the ‘license’ variable. Then finally we have copyright license’s URL which is stored under ‘licenceUrl’ variable of copyright object. The resulting UI from the above source code looks like this : Fig. 1: The user interface of the copyright details Now we need to test whether the copyright details are completely displayed or…

Continue ReadingImplementing Copyright API in Open Event Frontend

Ember Mixins used in Open Event Frontend

This post will illustrate how ember mixins are used in the Open Event Frontend to avoid code duplication and to keep it clean to reduce code complexity. The Open Event application needs forms at several places like at the time of login, for the creation of the event, taking the details of the user, creating discount codes for tickets etc. Every form performs similar actions which taking input and finally storing it in the database. In this process, a set of things keep executing in the background such as continuous form validation which means checking inputted values to ensure they are correctly matched with their type as provided in the validation rules, popping out an error message in case of wrong inputted values, etc. which is common to all the forms. The only thing which changes is the set of validation rules which are unique for every form. So let’s see how we solved this issue using Ember Mixins. While writing the code, we often run into the situation where we need to use the similar behaviour in different parts of the project. We always try not to duplicate the code and keep finding the ways to DRY ( Don’t Repeat Yourself ) up the code. In Ember, we can share the code in the different parts of the project using Ember.Mixins. While creating the forms, we mostly have differing templates but the component logic remains same. So we created a mixin form.js which contains all the properties which are common to all the forms. The code mixin contains can be reused throughout different parts of the application and is not a concern of any one route, controller, component, etc. The mixins don't get instantiated until we pass them into some object, and they get created in the order of which they're passed in. The code for form.js mixin looks like this. export default Mixin.create({ autoScrollToErrors : true, autoScrollSpeed : 200, getForm() { return this.get('$form'); }, onValid(callback) { this.getForm().form('validate form'); if (this.getForm().form('is valid')) { callback(); } }, didRender() { const $popUps = this.$('.has.popup'); if ($popUps) { $popUps.popup({ hoverable: true }); } let $form = this.$('.ui.form'); if ($form) { $form = $form.first(); if (this.get('getValidationRules') && $form) { $form.form(merge(defaultFormRules, this.getValidationRules())); } }, didInsertElement() { $.fn.form.settings.rules.date = (value, format = FORM_DATE_FORMAT) => { if (value && value.length > 0 && format) { return moment(value, format).isValid(); } return true; }; } The complete code can be seen here. Let’s start understanding the above code. In the first line, we created a mixin via Ember.Mixin.create() method. We have then specified the property ‘autoScrollToErrors’ to true so in case if there is some error, the form will automatically scroll to the error. The property ‘autoScrollSpeed’ specifies the speed with which the form will auto scroll to show the error. ‘getForm()’ method helps in getting the object which will be passed to the mixin. In ‘onValid()’ method we’re validating the form and passing the callbacks if it is correctly validated. We then have ‘didRender()’ method which renders…

Continue ReadingEmber Mixins used in Open Event Frontend

Testing Errors and Exceptions Using Unittest in Open Event Server

Like all other helper functions in FOSSASIA's Open Event Server, we also need to test the exception and error helper functions and classes. The error helper classes are mainly used to create error handler responses for known errors. For example we know error 403 is Access Forbidden, but we want to send a proper source message along with a proper error message to help identify and handle the error, hence we use the error classes. To ensure that future commits do not mismatch the error, we implemented the unit tests for errors. There are mainly two kind of error classes, one are HTTP status errors and the other are the exceptions. Depending on the type of error we get in the try-except block for a particular API, we raise that particular exception or error. Unit Test for Exception Exceptions are written in this form: @validates_schema def validate_quantity(self, data): if 'max_order' in data and 'min_order' in data: if data['max_order'] < data['min_order']: raise UnprocessableEntity({'pointer': '/data/attributes/max-order'}, "max-order should be greater than min-order")   This error is raised wherever the data that is sent as POST or PATCH is unprocessable. For example, this is how we raise this error: raise UnprocessableEntity({'pointer': '/data/attributes/min-quantity'},           "min-quantity should be less than max-quantity") This exception is raised due to error in validation of data where maximum quantity should be more than minimum quantity. To test that the above line indeed raises an exception of UnprocessableEntity with status 422, we use the assertRaises() function. Following is the code: def test_exceptions(self): # Unprocessable Entity Exception with self.assertRaises(UnprocessableEntity): raise UnprocessableEntity({'pointer': '/data/attributes/min-quantity'}, "min-quantity should be less than max-quantity") In the above code, with self.assertRaises() creates a context of exception type, so that when the next line raises an exception, it asserts that the exception that it was expecting is same as the exception raised and hence ensures that the correct exception is being raised Unit Test for Error In error helper classes, what we do is, for known HTTP status codes we return a response that is user readable and understandable. So this is how we raise an error: ForbiddenError({'source': ''}, 'Super admin access is required') This is basically the 403: Access Denied error. But with the “Super admin access is required” message it becomes far more clear. However we need to ensure that status code returned when this error message is shown still stays 403 and isn’t modified in future unwantedly. Here, errors and exceptions work a little different. When we declare a custom error class, we don’t really raise that error. Instead we show that error as a response. So we can’t use the assertRaises() function. However what we can do is we can compare the status code and ensure that the error raised is the same as the expected one. So we do this: def test_errors(self): with app.test_request_context(): # Forbidden Error forbidden_error = ForbiddenError({'source': ''}, 'Super admin access is required') self.assertEqual(forbidden_error.status, 403) # Not Found Error not_found_error = NotFoundError({'source': ''}, 'Object not found.') self.assertEqual(not_found_error.status, 404) Here we firstly create an…

Continue ReadingTesting Errors and Exceptions Using Unittest in Open Event Server

Checking Image Size to Avoid Crash in Android Apps

In Giggity app a user can create a shortcut for the event by clicking on “home shortcut” button in the navigation drawer. Open Event format provides the logo URL in the return data so we do not need to provide it separately in the app’s raw file. Sometimes the image can be too big to be put on screen as icon for shortcut. In this blog I describe a very simple method to check if we should use the image or not to avoid the crash and pixelation due to resolution. We can store the image received in bitmap format. A bitmap is a type of memory organization or image file format used to store digital images. The term bitmap comes from the computer programming terminology, meaning just a map of bits, a spatially mapped array of bits. By storing it in bitmap format we can easily get the necessary information about the image to check if it is suitable for use. We can use the BitmapFactory class which provides several decoding methods like (decodeByteArray(), decodeFile(), decodeResource(), etc.) for creating a Bitmap from various sources. Choose the most appropriate decode method based on your image data source. These methods attempt to allocate memory for the constructed bitmap and therefore can easily result in an OutOfMemory exception. Each type of decode method has additional signatures that let you specify decoding options via the BitmapFactory.Options class. Setting the inJustDecodeBounds property to true while decoding avoids memory allocation, returning null for the bitmap object but setting outWidth, outHeight and outMimeType. This technique allows you to read the dimensions and type of the image data prior to construction (and memory allocation) of the bitmap. BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; String imageType = options.outMimeType; To avoid java.lang.OutOfMemory exceptions, check the dimensions of a bitmap before decoding it, unless you absolutely trust the source to provide you with predictably sized image data that comfortably fits within the available memory. So here is the particular example from Giggity app, it avoids crash on the recieving a large image for the icon. So once we store the the image in bitmap format we check if the height and width of the icon is exceeding the maximum limit. public Bitmap getIconBitmap() { InputStream stream = getIconStream(); Bitmap ret = null; if (stream != null) { ret = BitmapFactory.decodeStream(stream); if (ret == null) { Log.w("getIconBitmap", "Discarding unparseable file"); return null; } if (ret.getHeight() > 512 || ret.getHeight() != ret.getWidth()) { Log.w("getIconBitmap", "Discarding, icon not square or >512 pixels"); return null; } if (!ret.hasAlpha()) { Log.w("getIconBitmap", "Discarding, no alpha layer"); return null; } } return ret; } If it does then we can avoid the icon. In this case we check if the icon is more than 512 pixels in height and width. If it is so then we could avoid it. We could also check if the icon has a transparent background by using “hasAlpha” so…

Continue ReadingChecking Image Size to Avoid Crash in Android Apps

Upgrading the Style and Aesthetic of an Android App using Material Design

I often encounter apps as I add Open Event format support that don’t follow current design guidelines. Earlier styling an app was a tough task as the color and behaviour of the views needed to be defined separately. But now as we move forward to advanced styling methods we can easily style our app. I recently worked on upgrading the user interface of Giraffe app after adding our Open Event support. See the repository to view the code for more reference. Here I follow the same procedure to upgrade the user interface. First we add essential libraries to move with our material aesthetic. The Appcompat library provides backward compatibility. //Essential Google libraries compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support:design:25.3.1 Then we define an XML file in the values folder for the style of the app which we get through Appcompat library. We could inherit same style in the entire app or separate style for the particular activity. <resources>   <!-- Base application theme. -->   <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">       <!-- Customize your theme here. -->       <item name="colorPrimary">@color/colorPrimary</item>       <item name="colorPrimaryDark">@color/colorPrimaryDark</item>       <item name="colorAccent">@color/colorAccent</item>   </style>   <style name="AlertDialogCustom" parent="Theme.AppCompat.Light.Dialog.Alert">       <item name="colorPrimary">@color/colorPrimary</item>       <item name="colorAccent">@color/colorAccent</item>   </style> </resources> So now we can see the views made following the same color scheme and behaviour throughout the app following current design guidelines without any particular manipulation to each of them. Tip: Don’t define values of colors separately for different views. Define them in colors.xml to use them everywhere. It becomes easier then to change in future if needed. The app now uses Action Bar for the frequently used operations unlike the custom layout that was made earlier. This is how Action Bar is implemented, First declare the action bar in XML layout, Tip: Define color of the bar two shades lighter than the status bar. <android.support.design.widget.AppBarLayout             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:background="@android:color/transparent"             android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">             <android.support.v7.widget.Toolbar                 xmlns:app="http://schemas.android.com/apk/res-auto"                          android:id="@+id/toolbar_options"                 android:layout_width="match_parent"                 android:layout_height="?attr/actionBarSize"                 android:background="@color/colorPrimary"                 app:popupTheme="@style/ThemeOverlay.AppCompat.Dark">                                  <TextView                     android:layout_width="wrap_content"                     android:layout_height="wrap_content"                     android:text="@string/options"                     android:textColor="@color/colorAccent"                     android:textSize="20sp" />              </android.support.v7.widget.Toolbar> </android.support.design.widget.AppBarLayout> Then you can use the action bar in the activity, use onCreateOptionsMenu() method to inflate options in the toolbar. @Override    public void onCreate(Bundle savedInstanceState) {        ...        setTitle("");        title = (TextView) findViewById(R.id.titlebar);        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar_main);        setSupportActionBar(toolbar);        ...    } The menu that needs to be inflated will be like this for two button at the right end of the action bar for bookmarks and filter respectively, <menu xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:app="http://schemas.android.com/apk/res-auto">     <item        android:id = "@+id/action_bookmark"        android:icon = "@drawable/ic_bookmark"        android:menuCategory = "secondary"        android:title = "Bookmark"        app:showAsAction = "ifRoom" />     <item         android:id = "@+id/action_filter"         android:icon = "@drawable/ic_filter"         android:menuCategory = "secondary"         android:title = "Filter"         app:showAsAction = "ifRoom" /> </menu> To adapt the declared style further, Alert Dialogs are also modified to match the app’s theme, it’s style is defined along with the app’s style. See below AlertDialog.Builder noFeedBuilder = new AlertDialog.Builder(context,R.style.AlertDialogCustom);            noFeedBuilder.setMessage(R.string.main_no_feed_text)                    .setTitle(R.string.main_no_feed_title)                    .setPositiveButton(R.string.common_yes, new DialogInterface.OnClickListener() {                  ...            noFeedBuilder.show(); Here is an example of improvement, before and after we update the user interface and aesthetic of app in easy steps defined,     See this for all the changes made to step up the user interface of the app. References: Google Android…

Continue ReadingUpgrading the Style and Aesthetic of an Android App using Material Design

Importing the Open Event format in Giraffe

Giraffe is a personal conference schedule tool for Android. Most conferences, bar camps and similar events offer their plan of sessions and talks in the iCal format for importing into your calendar. However importing a whole session plan into your standard calendar renders it pretty much useless for anything else. Giraffe allows users to import the schedule into a separate list giving you a simple overview on what happens on the conference. Besides the session, title, date and  time it also lists the speaker, location and description if available in the iCal URL. Sessions can be bookmarked and the list can be filtered by favourites and upcoming talks. Recently I added the support for Open Event JSON format along with iCal. In this blog I describe the simple steps you need to follow to see the event that is created in the Open Event server in the Giraffe app. The initial steps are similar to Giggity app,  1. Go to your event dashboard 2. Click on the export button. 3. Select sessions from the dashboard and copy the URL.   4. Click on the “Giraffe” button on the toolbar and paste the link in the box following. App will ask you to paste it when the first time you open it. Here the app loads the data and checks few initial character to see which kind of data is received. Find my other blog post to solve that problem here. The app uses separate data models for iCal and JSON to store the informations received and then save them in SQL database for CRUD options. See the database activity here     5. Now you can see the sessions. Click on them to see more information or bookmark them if needed. The data is loaded from the database so when app is offline so we don’t need to worry about connection once the data is being loaded.     Resources: Documentation for Giraffe app by https://www.splitbrain.org/projects/giraffe APK for testing https://github.com/heysadboy/open-event-apps/blob/master/apk/giraffe.apk  

Continue ReadingImporting the Open Event format in Giraffe

Open Event Server: Testing Image Resize Using PIL and Unittest

FOSSASIA's Open Event Server project uses a certain set of functions in order to resize image from its original, example to thumbnail, icon or larger image. How do we test this resizing of images functions in Open Event Server project? To test image dimensions resizing functionality, we need to verify that the the resized image dimensions is same as the dimensions provided for resize.  For example, in this function, we provide the url for the image that we received and it creates a resized image and saves the resized version. def create_save_resized_image(image_file, basewidth, maintain_aspect, height_size, upload_path, ext='jpg', remove_after_upload=False, resize=True): """ Create and Save the resized version of the background image :param resize: :param upload_path: :param ext: :param remove_after_upload: :param height_size: :param maintain_aspect: :param basewidth: :param image_file: :return: """ filename = '{filename}.{ext}'.format(filename=get_file_name(), ext=ext) image_file = cStringIO.StringIO(urllib.urlopen(image_file).read()) im = Image.open(image_file) # Convert to jpeg for lower file size. if im.format is not 'JPEG': img = im.convert('RGB') else: img = im if resize: if maintain_aspect: width_percent = (basewidth / float(img.size[0])) height_size = int((float(img.size[1]) * float(width_percent))) img = img.resize((basewidth, height_size), PIL.Image.ANTIALIAS) temp_file_relative_path = 'static/media/temp/' + generate_hash(str(image_file)) + get_file_name() + '.jpg' temp_file_path = app.config['BASE_DIR'] + '/' + temp_file_relative_path dir_path = temp_file_path.rsplit('/', 1)[0] # create dirs if not present if not os.path.isdir(dir_path): os.makedirs(dir_path) img.save(temp_file_path) upfile = UploadedFile(file_path=temp_file_path, filename=filename) if remove_after_upload: os.remove(image_file) uploaded_url = upload(upfile, upload_path) os.remove(temp_file_path) return uploaded_url In this function, we send the image url, the width and height to be resized to, and the aspect ratio as either True or False along with the folder to be saved. For this blog, we are gonna assume aspect ratio is False which means that we don’t maintain the aspect ratio while resizing. So, given the above mentioned as parameter, we get the url for the resized image that is saved. To test whether it has been resized to correct dimensions, we use Pillow or as it is popularly know, PIL. So we write a separate function named getsizes() within which get the image file as a parameter. Then using the Image module of PIL, we open the file as a JpegImageFile object. The JpegImageFile object has an attribute size which returns (width, height). So from this function, we return the size attribute. Following is the code: def getsizes(self, file): # get file size *and* image size (None if not known) im = Image.open(file) return im.size As we have this function, it’s time to look into the unit testing function. So in unit testing we set dummy width and height that we want to resize to, set aspect ratio as false as discussed above. This helps us to test that both width and height are properly resized. We are using a creative commons licensed image for resizing. This is the code: def test_create_save_resized_image(self): with app.test_request_context(): image_url_test = 'https://cdn.pixabay.com/photo/2014/09/08/17/08/hot-air-balloons-439331_960_720.jpg' width = 500 height = 200 aspect_ratio = False upload_path = 'test' resized_image_url = create_save_resized_image(image_url_test, width, aspect_ratio, height, upload_path, ext='png') resized_image_file = app.config.get('BASE_DIR') + resized_image_url.split('/localhost')[1] resized_width, resized_height = self.getsizes(resized_image_file) In the above code from create_save_resized_image, we receive the url…

Continue ReadingOpen Event Server: Testing Image Resize Using PIL and Unittest

Creating Unit Tests for File Upload Functions in Open Event Server with Python Unittest Library

In FOSSASIA's Open Event Server, we use the Python unittest library for unit testing various modules of the API code. Unittest library provides us with various assertion functions to assert between the actual and the expected values returned by a function or a module. In normal modules, we simply use these assertions to compare the result since the parameters mostly take as input normal data types. However one very important area for unittesting is File Uploading. We cannot really send a particular file or any such payload to the function to unittest it properly, since it expects a request.files kind of data which is obtained only when file is uploaded or sent as a request to an endpoint. For example in this function: def uploaded_file(files, multiple=False): if multiple: files_uploaded = [] for file in files: extension = file.filename.split('.')[1] filename = get_file_name() + '.' + extension filedir = current_app.config.get('BASE_DIR') + '/static/uploads/' if not os.path.isdir(filedir): os.makedirs(filedir) file_path = filedir + filename file.save(file_path) files_uploaded.append(UploadedFile(file_path, filename)) else: extension = files.filename.split('.')[1] filename = get_file_name() + '.' + extension filedir = current_app.config.get('BASE_DIR') + '/static/uploads/' if not os.path.isdir(filedir): os.makedirs(filedir) file_path = filedir + filename files.save(file_path) files_uploaded = UploadedFile(file_path, filename) return files_uploaded So, we need to create a mock uploading system to replicate this check. So inside the unittesting function we create an api route for this particular scope to accept a file as a request. Following is the code: @app.route("/test_upload", methods=['POST']) def upload(): files = request.files['file'] file_uploaded = uploaded_file(files=files) return jsonify( {'path': file_uploaded.file_path, 'name': file_uploaded.filename}) In the above code, it creates an app route with endpoint test_upload. It accepts a request.files. Then it sends this object to the uploaded_file function (the function to be unittested), gets the result of the function, and returns the result in a json format. With this we have the endpoint to mock a file upload ready. Next we need to send a request with file object. We cannot send a normal data which would then be treated as a normal request.form. But we want to receive it in request.files. So we create 2 different classes inheriting other classes. def test_upload_single_file(self): class FileObj(StringIO): def close(self): pass class MyRequest(Request): def _get_file_stream(*args, **kwargs): return FileObj() app.request_class = MyRequest MyRequest class inherits the Request class of Flask framework. We define the file stream of the Request class as the FileObj. Then, we set the request_class attribute of the Flask app to this new MyRequest class. After we have it all setup, we need to send the request and see if the uploaded file is being saved properly or not. For this purpose we take help of StringIO library. StringIO creates a file-like class which can be then used to replicate a file uploading system. So we send the data as {‘file’: (StringIO('1,2,3,4'), 'test_file.csv')}. We send this as data to the /test_upload endpoint that we have created previously. As a result, the endpoint receives the function, saves the file, and returns the filename and file_path for the stored file. with app.test_request_context(): client = app.test_client() resp = client.post('/test_upload',…

Continue ReadingCreating Unit Tests for File Upload Functions in Open Event Server with Python Unittest Library

Implementing Settings API on Open Event Frontend to View and Update Admin Settings

This blog article will illustrate how the admin settings are displayed and updated on the admin settings page in Open Event Frontend, using the settings API. It will also illustrate the use of the notification service to display corresponding notifications on whether the update operation is successful or not. Our discussion primarily will involve the admin/settings/index route to illustrate the process, all other admin settings route work exactly the same way. The primary end point of Open Event API with which we are concerned with for fetching tickets for an event is GET /v1/settings Since there are multiple  routes under admin/settings  including admin/settings/index, and they all will share the same setting model, it is efficient to make the call for Event on the settings route, rather than repeating it for each sub route, so the model for settings route is: model() { return this.store.queryRecord(setting, {}); } It is important to note that, we need not specify the model for index route or in fact for any of the sub routes of settings.  This is because it is the default behaviour of ember that if the model for a route is not found, it will automatically look for it in the parent  route.   And hence all that is needed to be done to make the model available in the system settings form  is to pass it while calling the form component. <div class="ui basic {{if isLoading 'loading' ''}} segment"> {{forms/admin/settings/system-form save='updateSettings' settings=model}} </div> Thus the model properties will be available in the form via settings alias. Next, we need to bind the value property  of the input fields to the corresponding model properties.  Here is a sample snippet on so as to how to achieve that, for the full code please refer to the codebase or the resources below. <div class="field"> {{ui-radio label=(t 'Development') current=settings.appEnvironment name='environment' value='development' onChange=(action (mut settings.appEnvironment))}} </div> <div class="field"> {{ui-radio label=(t 'Staging') current=settings.appEnvironment name='environment' value='staging'}} </div> <div class="field"> {{ui-radio label=(t 'Production') current=settings.appEnvironment name='environment' value='production'}} </div> <div class="field"> <label> {{t 'App Name'}} </label> {{input type='text' name='app_name' value=settings.appName}} </div> <div class="field"> <label> {{t 'Tagline'}} </label> {{input type='text' name='tag_line' value=settings.tagline}} </div> In the example above, appName, tagLine and appEnvironment are binded to the actual properties in the model. After the required changes have been done, the user next submits the form which triggers the submit action. If the validation is successful, the action updateSettings residing in the controller of the route is triggered, this is where the primary operations happen. updateSettings() { this.set('isLoading', true); let settings = this.get('model'); settings.save() .then(() => { this.set('isLoading', false); this.notify.success(this.l10n.t('Settings have been saved successfully.')); }) .catch(()=> { this.set('isLoading', false); this.notify.error(this.l10n.t('An unexpected error has occured. Settings not saved.')); }); } The controller action first sets the isLoading property to true. This adds the semantic UI class loading to the segment containing the form, and it and so the form goes in the loading state, to let the user know the requests is being processed. Then the save()  call occurs and this makes a PATCH request…

Continue ReadingImplementing Settings API on Open Event Frontend to View and Update Admin Settings

Mailing Attachments Using Terminal in Open Event Android

The latest version of Open Event Android App Generator, v2 lacked the feature of mailing the generated APK to the email ID that is entered at the start of the app generation process. This also included mailing the error logs in case of APK failure. This is an important feature for app generator because the process of app generation is a time taking one. The users have to wait for the app to be generated so that they can download the generated APK. To avoid this, the generator can automatically email the APK as soon as it is generated. I took up this issue a few days back and started working on it. I started with thinking about the ways through which it will be implemented. This required some discussions with the mentors and co-developers. We finalised on the following ways: Using Sendgrid Using SMTP I will be discussing the implementation of both of them in this blog. The code for APK mailing starts with the function call Notification.send in generator.py if completed and apk_path and not error: Notification.send( to=self.creator_email, subject='Your android application for %s has been generated ' % self.event_name, message='Hi,<br><br>' 'Your android application for the \'%s\' event has been generated. ' 'And apk file has been attached along with this email.<br><br>' 'Thanks,<br>' 'Open Event App Generator' % self.event_name, file_attachment=apk_path, via_api=self.via_api ) else: Notification.send( to=self.creator_email, subject='Your android application for %s could not generated ' % self.event_name, message='Hi,<br><br> ' 'Your android application for the \'%s\' event could not generated. ' 'The error message has been provided below.<br><br>' '<code>%s</code><br><br>' 'Thanks,<br>' 'Open Event App Generator' % (self.event_name, str(error) if error else ''), file_attachment=apk_path, via_api=self.via_api ) This leads me to the class Notification.py. It has three functions:- 1. send(to, subject, message, file_attachment, via_api) 2. send_mail_via_smtp(payload): 3. send_email_via_sendgrid(payload): As the name suggests, the first function: send(to, subject, message, file_attachment, via_api) mainly decides which service (out of smtp and sendgrid) should be used to send the email, on the basis of the input parameters (especially, the ‘EMAIL_SERVICE’ parameter that has to be set in config.py). The function looks like as follows: send(to, subject, message, file_attachment, via_api) It is in the send() that the other two functions are called. If the email_service is smtp, it calls the Notification.send_mail_via_smtp(payload). Otherwise, the Notification.send_email_via_sendgrid(payload) is called. The sendgrid function is pretty straightforward: @staticmethod def send_email_via_sendgrid(payload): key = current_app.config['SENDGRID_KEY'] if not key: logger.info('Sendgrid key not defined') return headers = { "Authorization": ("Bearer " + key) } requests.post( "https://api.sendgrid.com/api/mail.send.json", data=payload, headers=headers ) It requires a personalised sendgrid key which is accessed from the config.py file. Apart from that it handles some errors by giving logs in celery tasks. The main line in the function that initiates the email is a POST request made using the python library ‘requests’. The request is made as follows: requests.post( "https://api.sendgrid.com/api/mail.send.json", data=payload, headers=headers ) The send_mail_via_smtp(payload): function looks for some configurations before sending the mail: @staticmethod def send_mail_via_smtp(payload): """ Send email via SMTP :param config: :param payload: :return: """ smtp_encryption = current_app.config['SMTP_ENCRYPTION'] if smtp_encryption == 'tls':…

Continue ReadingMailing Attachments Using Terminal in Open Event Android