Implementing Session and Speaker Creation From Event Panel In Open Event Frontend

Open-Event Front-end uses Ember data for handling Open Event Orga API which abides by JSON API specs. It allows the user to manage the event using the event panel of that event. This panel lets us create or update sessions & speakers. Each speaker must be associated with a session, therefore we save the session before saving the speaker. In this blog we will see how to Implement the session & speaker creation via event panel. Lets see how we implemented it? Passing the session & speaker models to the form On the session & speaker creation page we need to render the forms using the custom form API and create new speaker and session entity. We create a speaker object here and we pass in the relationships for event and the user to it, likewise we create the session object and pass the event relationship to it. These objects along with form which contains all the fields of the custom form, tracks which is a list of all the tracks & sessionTypes which is a list of all the session types of the event is passed in the model. return RSVP.hash({ event : eventDetails, form : eventDetails.query('customForms', { 'page[size]' : 50, sort : 'id' }), session: this.get('store').createRecord('session', { event: eventDetails }), speaker: this.get('store').createRecord('speaker', { event : eventDetails, user : this.get('authManager.currentUser') }), tracks : eventDetails.query('tracks', {}), sessionTypes : eventDetails.query('sessionTypes', {}) }); We bind the speaker & session object to the template which has contains the session-speaker component for form validation. The request is only made if the form gets validated. Saving the data In Open Event API each speaker must be associated with a session, i.e we must define a session relationship for the speaker. To accomplish this we first save the session into the server and once it has been successfully saved we pass the session as a relation to the speaker object. this.get('model.session').save() .then(session => { let speaker = this.get('model.speaker'); speaker.set('session', session); speaker.save() .then(() => { this.get('notify').success(this.l10n.t('Your session has been saved')); this.transitionToRoute('events.view.sessions', this.get('model.event.id')); }); }) We save the objects using the save method. After the speakers and sessions are save successfully we notify the user by showing a success message via the notify service. Thank you for reading the blog, you can check the source code for the example here. Resources Official Ember Data documentation Learn more about how to make requests in ember from this blog by asommer70

Continue ReadingImplementing Session and Speaker Creation From Event Panel In Open Event Frontend

Creating Dynamic Forms Using Custom-Form API in Open Event Front-end

In Open Event Front-end allows the the event creators to customise the sessions & speakers forms which are implemented on the Orga server using custom-form API. While event creation the organiser can select the forms fields which will be placed in the speaker & session forms. In this blog we will see how we created custom forms for sessions & speakers using the custom-form API. Lets see how we did it. Retrieving all the form fields Each event has custom form fields which can be enabled on the sessions-speakers page, where the organiser can include/exclude the fields for speakers & session forms which are used by the organiser and speakers. return this.modelFor('events.view').query('customForms', {}); We pass return the result of the query to the new session route where we will create a form using the forms included in the event. Creating form using custom form API The model returns an array of all the fields related to the event, however we need to group them according to the type of the field i.e session & speaker. We use lodash groupBy. allFields: computed('fields', function() { return groupBy(this.get('fields').toArray(), field => field.get('form')); }) For session form we run a loop allFields.session which is an array of all the fields related to session form. We check if the field is included and render the field. {{#each allFields.session as |field|}} {{#if field.isIncluded}} <div class="field"> <label class="{{if field.isRequired 'required'}}" for="name">{{field.name}}</label> {{#if (or (eq field.type 'text') (eq field.type 'email'))}} {{#if field.isLongText}} {{widgets/forms/rich-text-editor textareaId=(if field.isRequired (concat 'session_' field.fieldIdentifier '_required'))}} {{else}} {{input type=field.type id=(if field.isRequired (concat 'session_' field.fieldIdentifier '_required'))}} {{/if}} {{/if}} </div> {{/if}} {{/each}} We also use a unique id for all the fields for form validation. If the field is required we create a unique id as `session_fieldName_required` for which we add a validation in the session-speaker-form component. We also use different components for different types of fields eg. for a long text field we make use of the rich-text-editor component. Thank you for reading the blog, you can check the source code for the example here. Resources Official Ember Data documentation Official Lodash documentation Blog on simple forms in ember by Gregg Bolinger

Continue ReadingCreating Dynamic Forms Using Custom-Form API in Open Event Front-end

Implementing Notifications in Open Event Server

In FOSSASIA’s Open Event Server project, along with emails, almost all actions have necessary user notifications as well. So, when a new session is created or a session is accepted by the event organisers, along with the email, a user notification is also sent. Though showing the user notification is mainly implemented in the frontend site but the content to be shown and on which action to show is strictly decided by the server project. A notification essentially helps an user to get the necessary information while staying in the platform itself and not needing to go to check his/her email for every action he performs. So unlike email which acts as a backup for the informations, notification is more of an instant thing. The API The Notifications API is mostly like all other JSON API endpoints in the open event project. However in Notifications API we do not allow any to send a POST request. The admin of the server is able to send a GET a request to view all the notifications that are there in the system while a user can only view his/her notification. As of PATCH we allow only the user to edit his/her notification to mark it as read or not read. Following is the schema for the API: class NotificationSchema(Schema): """ API Schema for Notification Model """ class Meta: """ Meta class for Notification API schema """ type_ = 'notification' self_view = 'v1.notification_detail' self_view_kwargs = {'id': '<id>'} self_view_many = 'v1.microlocation_list_post' inflect = dasherize id = fields.Str(dump_only=True) title = fields.Str(allow_none=True, dump_only=True) message = fields.Str(allow_none=True, dump_only=True) received_at = fields.DateTime(dump_only=True) accept = fields.Str(allow_none=True, dump_only=True) is_read = fields.Boolean() user = Relationship(attribute='user', self_view='v1.notification_user', self_view_kwargs={'id': '<id>'}, related_view='v1.user_detail', related_view_kwargs={'notification_id': '<id>'}, schema='UserSchema', type_='user' ) The main things that are shown in the notification from the frontend are the title and message. The title is the text that is shown without expanding the entire notification that gives an overview about the message in case you don’t want to read the entire message. The message however provides the entire detail that is associated with the action performed. The user relationship stores which user the particular notification is related with. It is a one-to-one relationship where one notification can only belong to one user. However one user can have multiple notifications. Another important attribute is the is_read attribute. This is the only attribute that is allowed to be changed. By default, when we make an entry in the database, is_read is set to FALSE. Once an user has read the notification, a request is sent from the frontend to change is_read to TRUE. The different actions for which we send notification are stored in the models/notification.py file as global variables. USER_CHANGE_EMAIL = "User email"' NEW_SESSION = 'New Session Proposal' PASSWORD_CHANGE = 'Change Password' EVENT_ROLE = 'Event Role Invitation' TICKET_PURCHASED = 'Ticket(s) Purchased' TICKET_PURCHASED_ATTENDEE = 'Ticket(s) purchased to Attendee ' EVENT_EXPORTED = 'Event Exported' EVENT_EXPORT_FAIL = 'Event Export Failed' EVENT_IMPORTED = 'Event Imported' HTML Templates The notification title and message that is stored in the…

Continue ReadingImplementing Notifications in Open Event Server

Implement Email in Open Event Server

In FOSSASIA’s Open Event Server project, we send out emails when various different actions are performed using the API. For example, when a new user is created, he/she receives an email welcoming him to the server as well as an email verification email. Users get role invites from event organisers in the form of emails, when someone buys a ticket he/she gets a PDF link to the ticket as email. So as you can understand all the important informations that are necessary to be notified to the user are sent as an email to the user and sometimes to the organizer as well. In FOSSASIA, we use sendgrid’s API or an SMTP server depending on the admin settings for sending emails. You can read more about how we use sendgrid’s API to send emails in FOSSASIA here. Now let’s dive into the modules that we have for sending the emails. The three main parts in the entire email sending are: Model - Storing the Various Actions Templates - Storing the HTML templates for the emails Email Functions - Individual functions for various different actions Let’s go through each of these modules one by one. Model USER_REGISTER = 'User Registration' USER_CONFIRM = 'User Confirmation' USER_CHANGE_EMAIL = "User email" INVITE_PAPERS = 'Invitation For Papers' NEXT_EVENT = 'Next Event' NEW_SESSION = 'New Session Proposal' PASSWORD_RESET = 'Reset Password' PASSWORD_CHANGE = 'Change Password' EVENT_ROLE = 'Event Role Invitation' SESSION_ACCEPT_REJECT = 'Session Accept or Reject' SESSION_SCHEDULE = 'Session Schedule Change' EVENT_PUBLISH = 'Event Published' AFTER_EVENT = 'After Event' USER_REGISTER_WITH_PASSWORD = 'User Registration during Payment' TICKET_PURCHASED = 'Ticket(s) Purchased' In the Model file, named as mail.py, we firstly declare the various different actions for which we send the emails out. These actions are globally used as the keys in the other modules of the email sending service. Here, we define global variables with the name of the action as strings in them. These are all constant variables, which means that there value remains throughout and never changes. For example, USER_REGISTER has the value ‘User Registration’, which essentially means that anything related to the USER_REGISTER key is executed when the User Registration action occurs. Or in other words, whenever an user registers into the system by signing up or creating a new user through the API, he/she receives the corresponding emails. Apart from this, we have the model class which defines a table in the database. We use this model class to store the actions performed while sending emails in the database. So we store the action, the time at which the email was sent, the recipient and the sender. That way we have a record about all the emails that were sent out via our server. class Mail(db.Model): __tablename__ = 'mails' id = db.Column(db.Integer, primary_key=True) recipient = db.Column(db.String) time = db.Column(db.DateTime(timezone=True)) action = db.Column(db.String) subject = db.Column(db.String) message = db.Column(db.String) def __init__(self, recipient=None, time=None, action=None, subject=None, message=None): self.recipient = recipient self.time = time if self.time is None: self.time = datetime.now(pytz.utc) self.action = action self.subject = subject…

Continue ReadingImplement Email in Open Event Server

Implementing Search Functionality In Calendar Mode On Schedule Page In Open Event Webapp

The schedule page in the event websites generated by the Open Event Webapp displays all the sessions of an event in a chronological manner. There are two modes on the page: List and the Calendar Mode. The former displays all the sessions in the form of a list while the latter displays them in a graphical grid or a rectangular format.  Below are the screenshots of the modes: List Mode Calendar Mode The list mode of the page already supported the search feature. We needed to implement it in the calendar mode. The corresponding issue for this feature is here. The whole work can be seen here. First, we see the basic structure of the page in the calendar mode. <div class="{{slug}} calendar"> <!-- slug represents the currently selected date --> <!-- This div contains all the sessions scheduled on the selected date --> <div class="rooms"> <!-- This div contains all the rooms of an event --> <!-- Each particular room has a set of sessions associated with it on that particular date --> <div class="room"> <!-- This div contains the list of session happening in a particular room --> <div class="session"> <!-- This div contains all the information about a session --> <div class="session-name"> {{title}} </div> <!-- Title of the session --> <h4 class="text"> {{{description}}} </h4> <!-- Description of the session --> <!-- This div contains the info of the speakers presenting the session --> <div class="session-speakers-list"> <div class="speaker-name"><strong>{{{title}}}</div> <!-- Name of the speaker --> <div class="session-speakers-more"> {{position}} {{organisation}} </div> <!-- Position and organization of speaker--> </div> </div> </div> </div> </div> </div> The user will type the query in the search bar near the top of the page. The search bar has the class fossasia-filter. We set up a keyup event listener on that element so that whenever the user will press and release a key, we will invoke the event handler function which will display only those elements which match the current query entered in the search bar. This way, we are able to change the results of the search dynamically on user input. Whenever a single key is pressed and lifted off, the event is fired which invokes the handler and the session elements are filtered accordingly. Now the important part is how we actually display and hide the session elements. We actually compare few session attributes to the text entered in the search box. The text attributes that we look for are the title of the session, the name, position , and organization of the speaker(s) presenting the session. We check whether the text entered by the user in the search bar appears contiguously in any of the above-mentioned attributes or not. If it appears, then the session element is shown. Otherwise, its display is set to hidden. The checking is case insensitive. We also count the number of the visible sessions on the page and if it is equal to zero, display a message saying that no results were found. For example:- Suppose the user enters the string ‘wel’ in…

Continue ReadingImplementing Search Functionality In Calendar Mode On Schedule Page In Open Event Webapp

Customising URL Using Custom Adapters in Open Event Front-end

Open-Event Front-end uses Ember data for handling Open Event Orga API which abides by JSON API specs. The API has relationships which represent models in the database, however there are some API endpoints for which the URL is not direct. We make use of custom adapter to build a custom URL for the requests. In this blog we will see how to Implement relationships which do not have a model in the API server. Lets see how we implemented the admin-statistics-event API using custom adapter? Creating Order-statistics model To create a new model we use ember-cli command: ember g model admin-statistics-event The generated model: export default ModelBase.extend({ draft : attr('number'), published : attr('number'), past : attr('number') }) The API returns 3 attributes namely draft, published & past which represent the total number of drafted, live and past event in the system. The admin-statistics-event is an admin related model. Creating custom adapter To create a new adapter we use ember-cli command: ember g adapter event-statistics-event If we try to do a GET request the URL for the request will be ‘v1/admin-statistics-event’ which is an incorrect endpoint. We create a custom adapter to override the buildURL method. buildURL(modelName, id, snapshot, requestType, query) { let url = this._super(modelName, id, snapshot, requestType, query); url = url.replace('admin-statistics-event', 'admin/statistics/event'); return url; } We create a new variable url which holds the url generated by the buildURL method of the super adapter. We call the super method using ‘this._super’. We will now replace the ‘admin-statistics-event’ with ‘admin/statistics/event’ in url variable. We return the new url variable. This results in generation of correct URL for the request. Thank you for reading the blog, you can check the source code for the example here. Resources Official Ember Data documentation Ember custom adapter guide

Continue ReadingCustomising URL Using Custom Adapters in Open Event Front-end

Create Event by Importing JSON files in Open Event Server

Apart from the usual way of creating an event in  FOSSASIA’s Orga Server project by using POST requests in Events API, another way of creating events is importing a zip file which is an archive of multiple JSON files. This way you can create a large event like FOSSASIA with lots of data related to sessions, speakers, microlocations, sponsors just by uploading JSON files to the system. Sample JSON file can be found in the open-event project of FOSSASIA. The basic workflow of importing an event and how it works is as follows: First step is similar to uploading files to the server. We need to send a POST request with a multipart form data with the zipped archive containing the JSON files. The POST request starts a celery task to start importing data from JSON files and storing them in the database. The celery task URL is returned as a response to the POST request. You can use this celery task for polling purposes to get the status. If the status is FAILURE, we get the error text along with it. If status is SUCCESS we get the resulting event data In the celery task, each JSON file is read separately and the data is stored in the db with the proper relations. Sending a GET request to the above mentioned celery task, after the task has been completed returns the event id along with the event URL. Let’s see how each of these points work in the background. Uploading ZIP containing JSON Files For uploading a zip archive instead of sending a JSON data in the POST request we send a multipart form data. The multipart/form-data format of sending data allows an entire file to be sent as a data in the POST request along with the relevant file informations. One can know about various form content types here . An example cURL request looks something like this: curl -H "Authorization: JWT <access token>" -X POST -F 'file=@event1.zip' http://localhost:5000/v1/events/import/json The above cURL request uploads a file event1.zip from your current directory with the key as ‘file’ to the endpoint /v1/events/import/json. The user uploading the feels needs to have a JWT authentication key or in other words be logged in to the system as it is necessary to create an event. @import_routes.route('/events/import/<string:source_type>', methods=['POST']) @jwt_required() def import_event(source_type): if source_type == 'json': file_path = get_file_from_request(['zip']) else: file_path = None abort(404) from helpers.tasks import import_event_task task = import_event_task.delay(email=current_identity.email, file=file_path, source_type=source_type, creator_id=current_identity.id) # create import job create_import_job(task.id) # if testing if current_app.config.get('CELERY_ALWAYS_EAGER'): TASK_RESULTS[task.id] = { 'result': task.get(), 'state': task.state } return jsonify( task_url=url_for('tasks.celery_task', task_id=task.id) ) After the request is received we check if a file exists in the key ‘file’ of the form-data. If it is there, we save the file and get the path to the saved file. Then we send this path over to the celery task and run the task with the .delay() function of celery. After the celery task is started, the corresponding data about the import job is…

Continue ReadingCreate Event by Importing JSON files in Open Event Server

Handling Requests for hasMany Relationships in Open Event Front-end

In Open Event Front-end we use Ember Data and JSON API specs to integrate the application with the server. Ember Data provides an easy way to handle API requests, however it does not support a direct POST for saving bulk data which was the problem we faced while implementing event creation using the API. In this blog we will take a look at how we implemented POST requests for saving hasMany relationships, using an example of sessions-speakers route to see how we saved the tracks, microlocations & session-types. Lets see how we did it. Fetching the data from the server Ember by default does not support hasMany requests for getting related model data. However we can use external add on which enable the hasMany Get requests, we use ember-data-has-many-query which is a great add on for querying hasMany relations of a model. let data = this.modelFor('events.view.edit'); data.tracks = data.event.get('tracks'); data.microlocations = data.event.get('microlocations'); data.sessionTypes = data.event.get('sessionTypes'); return RSVP.hash(data); In the above example we are querying the tracks, microlocations & sessionTypes which are hasMany relationships, related to the events model. We can simply do a to do a GET request for the related model. data.event.get('tracks'); In the above example we are retrieving the all the tracks of the event. Sending a POST request for hasMany relationship Ember currently does not saving bulk data POST requests for hasMany relations. We solved this by doing a POST request for individual data of the hasMany array. We start with creating a `promises` array which contains all the individual requests. We then iterate over all the hasMany relations & push it to the `promises` array. Now each request is an individual promise. let promises = []; promises.push(this.get('model.event.tracks').toArray().map(track => track.save())); promises.push(this.get('model.event.sessionTypes').toArray().map(type => type.save())); promises.push(this.get('model.event.microlocations').toArray().map(location => location.save())); Once we have all the promises we then use RSVP to make the POST requests. We make use of all() method which takes an array of promises as parameter and resolves all the promises. If the promises are not resolved successfully then we simply notify the user using the notify service, else we redirect to the home page. RSVP.Promise.all(promises) .then(() => { this.transitionToRoute('index'); }, function() { this.get('notify').error(this.l10n.t(‘Data did not save. Please try again')); }); The result of this we can now retrieve & create new tracks, microlocations & sessionTypes on sessions-speakers route. Thank you for reading the blog, you can check the source code for the example here. Resources Official Ember Data documentation Official JSON API specs Blog on ember data by Pooyan Khosravy  

Continue ReadingHandling Requests for hasMany Relationships in Open Event Front-end

Export an Event using APIs of Open Event Server

We in FOSSASIA’s Open Event Server project, allow the organizer, co-organizer and the admins to export all the data related to an event in the form of an archive of JSON files. This way the data can be reused in some other place for various different purposes. The basic workflow is something like this: Send a POST request in the /events/{event_id}/export/json with a payload containing whether you require the various media files. The POST request starts a celery task in the background to start extracting data related to event and jsonifying them The celery task url is returned as a response. Sending a GET request to this url gives the status of the task. If the status is either FAILED or SUCCESS then there is the corresponding error message or the result. Separate JSON files for events, speakers, sessions, micro-locations, tracks, session types and custom forms are created. All this files are then archived and the zip is then served on the endpoint /events/{event_id}/exports/{path} Sending a GET request to the above mentioned endpoint downloads a zip containing all the data related to the endpoint. Let’s dive into each of these points one-by-one POST request ( /events/{event_id}/export/json) For making a POST request you firstly need a JWT authentication like most of the other API endpoints. You need to send a payload containing the settings for whether you want the media files related with the event to be downloaded along with the JSON files. An example payload looks like this: {  "image": true,  "video": true,  "document": true,  "audio": true } def export_event(event_id): from helpers.tasks import export_event_task settings = EXPORT_SETTING settings['image'] = request.json.get('image', False) settings['video'] = request.json.get('video', False) settings['document'] = request.json.get('document', False) settings['audio'] = request.json.get('audio', False) # queue task task = export_event_task.delay( current_identity.email, event_id, settings) # create Job create_export_job(task.id, event_id) # in case of testing if current_app.config.get('CELERY_ALWAYS_EAGER'): # send_export_mail(event_id, task.get()) TASK_RESULTS[task.id] = { 'result': task.get(), 'state': task.state } return jsonify( task_url=url_for('tasks.celery_task', task_id=task.id) ) Taking the settings about the media files and the event id, we pass them as parameter to the export event celery task and queue up the task. We then create an entry in the database with the task url and the event id and the user who triggered the export to keep a record of the activity. After that we return as response the url for the celery task to the user. If the celery task is still underway it show a response with ‘state’:’WAITING’. Once, the task is completed, the value of ‘state’ is either ‘FAILED’ or ‘SUCCESS’. If it is SUCCESS it returns the result of the task, in this case the download url for the zip. Celery Task to Export Event Exporting an event is a very time consuming process and we don’t want that this process to come in the way of user interaction with other services. So we needed to use a queueing system that would queue the tasks and execute them in the background with disturbing the main worker from executing the other user…

Continue ReadingExport an Event using APIs of Open Event Server

Uploading Files via APIs in the Open Event Server

There are two file upload endpoints. One is endpoint for image upload and the other is for all other files being uploaded. The latter endpoint is to be used for uploading files such as slides, videos and other presentation materials for a session. So, in FOSSASIA’s Orga Server project, when we need to upload a file, we make an API request to this endpoint which is turn uploads the file to the server and returns back the url for the uploaded file. We then store this url for the uploaded file to the database with the corresponding row entry. Sending Data The endpoint /upload/file  accepts a POST request, containing a multipart/form-data payload. If there is a single file that is uploaded, then it is uploaded under the key “file” else an array of file is sent under the key “files”. A typical single file upload cURL request would look like this: curl -H “Authorization: JWT <key>” -F file=@file.pdf -x POST http://localhost:5000/v1/upload/file A typical multi-file upload cURL request would look something like this: curl -H “Authorization: JWT <key>” -F files=@file1.pdf -F files=@file2.pdf -x POST http://localhost:5000/v1/upload/file Thus, unlike other endpoints in open event orga server project, we don’t send a json encoded request. Instead it is a form data request. Saving Files We use different services such as S3, google cloud storage and so on for storing the files depending on the admin settings as decided by the admin of the project. One can even ask to save the files locally by passing a GET parameter force_local=true. So, in the backend we have 2 cases to tackle- Single File Upload and Multiple Files Upload. Single File Upload if 'file' in request.files: files = request.files['file'] file_uploaded = uploaded_file(files=files) if force_local == 'true': files_url = upload_local( file_uploaded, UPLOAD_PATHS['temp']['event'].format(uuid=uuid.uuid4()) ) else: files_url = upload( file_uploaded, UPLOAD_PATHS['temp']['event'].format(uuid=uuid.uuid4()) ) We get the file, that is to be uploaded using request.files[‘file’] with the key as ‘file’ which was used in the payload. Then we use the uploaded_file() helper function to convert the file data received as payload into a proper file and store it in a temporary storage. After this, if force_local is set as true, we use the upload_local helper function to upload it to the local storage, i.e. the server where the application is hosted, else we use whatever service is set by the admin in the admin settings. In uploaded_file() function of helpers module, we extract the filename and the extension of the file from the form-data payload. Then we check if the suitable directory already exists. If it doesn’t exist, we create a new directory and then save the file in the directory 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) After that the upload function gets the settings key for either s3 or google storage and then uses the corresponding functions to upload this temporary file to the storage. Multiple File Upload elif 'files[]' in request.files:…

Continue ReadingUploading Files via APIs in the Open Event Server