Loading Default System Image of Event Topic on Open Event Server

In this blog, we will talk about how to add feature of loading system image of event topic from server to display it on Open Event Server. The focus is on adding a helper function to create system image and loading that local image onto server.

Helper function

In this feature, we are providing feature of addition of loading default system image if user doesn’t provides that.

  1. First we get a suitable filename for a image file using get_file_name() function.
  2. After getting filename, we check if the url provided by user is a valid url or not.
  3. If the url is invalid then we use the default system image as the image of that particular event topic.
  4. After getting the local image then we read that image, if the given image file or the default image is not readable or gives IOError then we send a message to the user that Image url is invalid.
  5. After successful reading of image we upload the image to event_topic directory in static directory of the project.
  6. After uploading of this image we get a local URL which shows where is the image is stored. This path is stored into database and finally we can display this image.

Resources

Continue Reading

Adding Custom System Roles API on Open Event Server

In this blog, we will talk about how to add API for accessing the Custom System Roles on Open Event Server. The focus is on Schema creation and it’s API creation.

Schema Creation

For the CustomSystemRoleSchema, we’ll make our Schema as follows

Now, let’s try to understand this Schema.

In this feature, we are providing Admin the rights to get and create more system roles.

  1. First of all, we are provide the two fields in this Schema, which are id and name.
  2. The very first attribute id should be of type string as it would have the identity which will auto increment when a new system role is created. Here dump_only means that this value can’t be changed after the record is created.
  3. Next attribute name should be of string type and it will contain the name of new custom system role. This attribute is required in a custom_system_roles table.

API Creation

For the Custom System Roles, we’ll make our API as follows

Now, let’s try to understand this Schema.

In this API, we are providing Admin the rights to set Custom System roles.

  1. CustomSystemRoleList inherits ResourceList which will give us list of all the custom system roles in the whole system.
  2. CustomSystemRoleList has a decorators attribute which gives the permission of POST request to only admins of the system.
  3. CustomSystemRoleDetail inherits ResourceDetail which will give the details of a CustomSystemRole object by id.
  4. CustomSystemRoleDetail has a decorators attribute which gives the permission of PATCH and DELETE requests to only admins of the system.

So, we saw how Custom System Role Schema and API is created to allow users to get it’s values and Admin users to update and delete it’s record.

Resources

Continue Reading

Adding Panel Permissions API in Open Event Server

In this blog, we will talk about how to add API for accessing the Panel Permissions on Open Event Server. The focus is on Schema creation and it’s API creation.

Schema Creation

For the PanelPermissionSchema, we’ll make our Schema as follows

Now, let’s try to understand this Schema.

In this feature, we are providing Admin the rights to create and assign panel permission to any of the custom system role.

  1. First of all, we are provide the four fields in this Schema, which are id, panel_name, role_id and can_access.
  2. The very first attribute id should be of type string as it would have the identity which will auto increment when a new system role is created. Here dump_only means that this value can’t be changed after the record is created.
  3. Next attribute panel_name should be of string type and it will contain the name of panel. This attribute is required in a panel_permissions table so set as allow_none=False.
  4. Next attribute role_id should be of integer type as it will tell us that to which role current panel is concerning.
  5. Next attribute can_access should be of boolean type as it will tell us whether a role of id=role_id has access to this panel or not.
  6. There is also a relationship named role which will give us the details of the custom system role with id=role_id.

API Creation

For the Panel Permissions, we’ll make our API as follows

Now, let’s try to understand this Schema.

In this API, we are providing Admin the rights to set panel permissions for a custom system role.

  1. PanelPermissionList inherits ResourceList which will give us list of all the custom system roles in the whole system.
  2. PanelPermissionList has a decorators attribute which gives the permission of both GET and POST requests to only admins of the system.
  3. The POST request of PanelPermissionList API requires the relationship of role.
  4. PanelPermissionDetail inherits ResourceDetail which will give the details of a Panel Permission object by id.
  5. PanelPermissionDetail has a decorators attribute which gives the permission of GET, PATCH and DELETE requests to only admins of the system.

So, we saw how Panel Permissions Schema and API is created to allow Admin users to get, update and delete it’s record.

Resources

 

Continue Reading

Adding support for rich text in Eventyay Organizer App

The Open Event Organizer App provides the users with its one of the core features of the ability to create or update an event. To add this feature, we will use HTML and android’s WebView in order to aid us integrate support for rich text in the app.

The first step is adding the Wasabeef RichText library to your project. Open your build.gradle file and add the support library to the dependency section.

Adding the dependency in build.gradle(app-level) in the project:

dependencies {
   //Other dependencies
   //Rich text editor
   implementation “jp.wasabeef:richeditor-android:1.2.2”
}

What we need is an activity accessible throughout the project for any element that needs to input rich text, which means that this activity goes in the utils package.

Let’s start with building this activity:

We need to first set a hint text or a placeholder text for the editor in case there’s no saved text for the concerned field.

public class RichEditorActivity extends AppCompatActivity {
   

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       

       binding.editor.setPlaceholder(getString(R.string.enter_text));
       Intent intent = getIntent();
       if (intent != null) {
         description = intent.getStringExtra(TAG_RICH_TEXT);
         if (!TextUtils.isEmpty(description) && !description.equals(getString(R.string.describe_event))) {
           binding.editor.setHtml(description);
         }
       }
}

Now let’s see how we add the formatted text to our WebView. Currently we are supporting the options:

  • Undo
  • Redo
  • Bold
  • Italic
  • StrikeThrough
  • Bulleted list
  • Numbered list

as follows:

binding.actionUndo.setOnClickListener(v > binding.editor.undo());
binding.actionRedo.setOnClickListener(v > binding.editor.redo());
binding.actionBold.setOnClickListener(v > binding.editor.setBold());
binding.actionItalic.setOnClickListener(v > binding.editor.setItalic());
binding.actionStrikethrough.setOnClickListener(v > binding.editor.setStrikeThrough());
binding.actionInsertBullets.setOnClickListener(v > binding.editor.setBullets());
binding.actionInsertNumbers.setOnClickListener(v > binding.editor.setNumbers());

To add support for adding links, we need to first setup a click listener and show a dialog on click:

binding.actionInsertLink.setOnClickListener(v -> {
   if (linkDialog == null) {
       createLinkDialog();
   }
   linkDialog.show();
});

The above listener is using a linkDialog, which is initialized in the method below, as follows:

We are first dynamically creating a LinearLayout, and then 2 EditTextViews, and then adding those views to the LinearLayout. Finally we build the AlertDialog as usual and set the Dialog’s view to the LinearLayout we created.

private void createLinkDialog() {
   LinearLayout layout = new LinearLayout(this);
   layout.setOrientation(LinearLayout.VERTICAL);
   final EditText text = new EditText(this);
   text.setHint(getString(R.string.text));
   layout.addView(text);
   final EditText link = new EditText(this);
   link.setHint(getString(R.string.insert_url));
   layout.addView(link);

   linkDialog = new AlertDialog.Builder(this)
      .setPositiveButton(getString(R.string.create), (dialog, which) -> {
           binding.editor.insertLink(link.getText().toString(), text.getText().toString());
      })
      .setNegativeButton(getString(R.string.cancel), (dialog, which) -> {
          dialog.dismiss();
      })
      .setView(layout)
      .create();
}

Here’s how the result looks like:

Resources

Continue Reading

Adding device names’ support for check-ins to Open Event Server

The Open Event Server provides backend support to Open Event Organizer Android App which is used to check-in attendees in an event. When checking in attendees, it is important for any event organizer to keep track of the device that was used to check someone in. For this, we provide an option in the Organizer App settings to set the device name. But this device name should have support in the server as well.

The problem is to be able to add device name data corresponding to each check-in time. Currently attendees model has an attribute called `checkin-times`, which is a csv of time strings. For each value in the csv, there has to be a corresponding device name value. This could be achieved by providing a similar csv key-value pair for “device-name-checkin”.

The constraints that we need to check for while handling device names are as follows:

  • If there’s `device_name_checkin` in the request, there must be `is_checked_in` and `checkin_times` in the data as well.
  • Number of items in checkin_times csv in data should be equal to the length of the device_name_checkin csv.
  • If there’s checkin_times in data, and device-name-checkin is absent, it must be set to `-` indicating no set device name.
if ‘device_name_checkin’ in data and data[‘device_name_checkin’] is not None:
  if ‘is_checked_in’ not in data or not data[‘is_checked_in’]:
       raise UnprocessableEntity(
           {‘pointer’: ‘/data/attributes/device_name_checkin’},
           “Attendee needs to be checked in first”
       )
  elif ‘checkin_times’ not in data or data[‘checkin_times’] is None:
      raise UnprocessableEntity(
          {‘pointer’: ‘/data/attributes/device_name_checkin’},
           “Check in Times missing”
      )
  elif len(data[‘checkin_times’].split(“,”)) != len(data[‘device_name_checkin’].split(“,”)):
     raise UnprocessableEntity(
           {‘pointer’: ‘/data/attributes/device_name_checkin’},
           “Check in Times missing for the corresponding device name”
         )
 if ‘checkin_times’ in data:
   if ‘device_name_checkin’ not in data or data[‘device_name_checkin’] is None:
       data[‘device_name_checkin’] = ‘-‘

The case is a little different for a PATCH request since we need to check for the number of items differently like this:

if ‘device_name_checkin’ in data and data[‘device_name_checkin’] is not None:
            if obj.device_name_checkin is not None:
               data[‘device_name_checkin’] = ‘{},{}’.format(obj.device_name_checkin, data[‘device_name_checkin’])                                                   
            if len(data[‘checkin_times’].split(“,”)) != len(data[‘device_name_checkin’].split(“,”)):
               raise UnprocessableEntity(
                   {‘pointer’: ‘/data/attributes/device_name_checkin’},
                   “Check in Time missing for the corresponding device name”)

Since we expect only the latest value to be present in a PATCH request, we first add it to the object by formatting using:

'{},{}'.format(obj.device_name_checkin, data['device_name_checkin'])

and then compare the length of the obtained CSVs for check in times and device names, so that corresponding to each check in time, we have either a device name or the default fill in value ‘-’.

That’s all. Read the full code here.

Requests and Responses:

Resources

  1. SQLAlchemy Docs
    https://docs.sqlalchemy.org/en/latest/
  2. Alembic Docs
    http://alembic.zzzcomputing.com/en/latest/
  3. Flask REST JSON API Classical CRUD operation
    https://flask-rest-jsonapi.readthedocs.io/en/latest/quickstart.html#classical-crud-operations
Continue Reading

Adding Events’ Payment Preferences to Eventyay Organizer Android App

The Open Event Organizer Android App allows creating events from the app itself. But organizers had to enter the payment information every time. To solve this problem, the PR#1058 was opened which saves the Organizers’ payment preferences in Event Settings.

The Open Event project offers 5 types of payment options:

Online:
1. Paypal
2. Stripe

Offline:
3. Cash payment
4. Bank Transfer
5. Cheques

Each of the above need the payment specific details to be saved. And stuffing all of them into a single Event Settings screen isn’t a good option. Therefore the following navigation was desired:

Event Settings -> Payment Preferences -> All options with their preferences

Android Developer guide references a simple method to achieve the above, which is by using nested preference screens. But unfortunately, there’s a bug in the support library and it cannot be implemented with  PreferenceFragmentCompat

So we had to apply a hack to the UI. We set an OnPreferenceClickListener as follows:

public class EventSettingsFragment extends PreferenceFragmentCompat {
   …
   @Override
   public void onCreatePreferencesFix(@Nullable Bundle bundle, String rootKey) {
       …

       findPreference(“payment_preferences”).setOnPreferenceClickListener(preference -> {
           FragmentTransaction transaction = getFragmentManager().beginTransaction();
           transaction
               .replace(R.id.fragment_container, PaymentPrefsFragment.newInstance())
               .addToBackStack(null)
               .commit();
           return true;
       });
   }
   …
}

Once the preference item “Payment Preferences” is clicked, we initiate a fragment transaction opening the Payment Preferences screen, and add it to the fragment back stack.

For each payment option, we have two things to consider:

  1. Is that payment option supported by the organizer?
  2. If yes, we need to store the necessary details in order to direct the payment to the organizer.

We are also keeping track of whether the organizer wants to keep using the same payment preferences for future events as well. This way we save the organizer’s effort of entering payment details for each new event.

<?xml version=“1.0” encoding=“utf-8”?>
<PreferenceScreen xmlns:android=“http://schemas.android.com/apk/res/android”>

   <CheckBoxPreference
       android:key=“use_payment_prefs”
       android:title=“@string/use_payment_prefs”
       android:summaryOn=“@string/using_payment_preferences”
       android:summaryOff=“@string/not_using_payment_preferences”
       android:defaultValue=“false” />

   <PreferenceCategory
       android:title=“Bank Transfer”>
       <CheckBoxPreference
           android:key=“accept_bank_transfers”
           android:title=“@string/accept_payment_by_bank_transfer”
           android:defaultValue=“false”/>

       <EditTextPreference
           android:key=“bank_details”
           android:title=“@string/bank_details” />
   </PreferenceCategory>
   …

</PreferenceScreen>

Now the only thing remaining is to set payment preferences once the Event Creation form is opened. Hence the following method is called in  CreateEventPresenter  sets the payment details for each option that the organizer has already saved the information for. It does this by using constants named like PREF… all declared in the  Constants.java  file.

using a custom Preference class which abstracts away some boilerplate code for us.

   public void setPaymentPreferences(Preferences preferences) {

       if (preferences.getBoolean(PREF_USE_PAYMENT_PREFS, false)) {
           
           event.setCanPayByBank(
               preferences.getBoolean(PREF_ACCEPT_BANK_TRANSFER, false)
           );
           event.setBankDetails(
               preferences.getString(PREF_BANK_DETAILS, null)
           );
           …
           getView().setPaymentBinding(event);
       }
   }

This is how the result looks like:

Resources

Continue Reading

Create Call for Speakers with Open Event Organizer Android App

In The Open Event Organizer Android app, we were providing variety of features to the user but the functionality to create call for speakers for an Event was missing. This feature was required to attract speakers from all over the world to participate in the event and make it a success. Theis blog explains how we added this feature to the project us following MVVM Architecture and using libraries like Retrofit, RxJava, Db Flow etc.

Objective

The goal will be to provide an option to the organizer to create Call for Speakers for an Event (if it doesn’t exist already). We will provide the organizer a Floating Action Button in Speakers Call detail layout which will open a fragment which contain all the relevant fields provided by Open Event Server. The organizer can fill up all the fields as per requirement and create the call for speakers.

Specifications

Let’s move on to the implementation details.

First, we will create the model which will contain all the fields offered by server. We are using Lombok library to reduce boilerplate code using annotations. For example, @Data annotation is used to generate getters and setters. @NoArgsConstructor to generate no arguments constructor as the name suggests. Along with that we are using @JonNaming annotation provided by Jackson library to serialize the model using KebabCaseStrategy. Also, the Table annotation provided by Raziz DbFlow library to make a table in database for this model.

@Data
@Type(“speakers-call”)
@NoArgsConstructor
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
@Table(database = OrgaDatabase.class, allFields = true)
public class SpeakersCall {@Id(LongIdHandler.class)
@PrimaryKey
public Long id;@Relationship(“event”)
@ForeignKey(onDelete = ForeignKeyAction.CASCADE)
public Event event;

public String announcement;
public String hash;
public String privacy;
public String startsAt;
public String endsAt;
}

To handle this problem we will be using Retrofit 2.3.0 to make Network Requests, which is a REST client for Android and Java by Square Inc. It makes it relatively easy to retrieve and upload JSON (or other structured data) via a REST based Web Service. Also we will be using other awesome libraries like RxJava 2.1.10 (by ReactiveX) to handle tasks asynchronously, Jackson, Jasminb-Json-Api in an MVVM architecture.

We will make a POST request to the server . So we specify the declaration in SpeakersCallApi.java

@POST(“speakers-calls”)
Observable<SpeakersCall> postSpeakersCall(@Body SpeakersCall speakersCall);

We will use the method createSpeakersCall(SpeakersCall speakersCall) in SpeakersCallRepositoryImpl.java to interact with the SpeakersCallApi and make the network request for us. The response will be passed on to the SpeakersCallViewModel method which calls it.

@Override
public Observable<SpeakersCall> createSpeakersCall(SpeakersCall speakersCall) {
if (!repository.isConnected()) {
return Observable.error(new Throwable(Constants.NO_NETWORK));
}return speakersCallApi
.postSpeakersCall(speakersCall)
.doOnNext(created -> {
created.setEvent(speakersCall.getEvent());
repository
.save(SpeakersCall.class, created)
.subscribe();
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}

Next, we will see the code for SpeakersCallViewModel which is using MVVM ViewModel to handle the processing logic for SpeakesCallFragment. Here we are passing SpeakersCallRepository instance in constructor. Methods like getProgress, getError and getSuccess pass on a LiveData Object from ViewModel to Fragment and the latter observes the changes on the object and show these in UI. CreateSpeakersCall method is used to call speakersCallRepository to send a POST request to server asynchronously using Retrofit and RxJava. Further, initialize method is used to set initial values of time and date to SpeakersCall object.

public class CreateSpeakersCallViewModel extends ViewModel {
public void createSpeakersCall(long eventId) {
if (!verify())
return;Event event = new Event();event.setId(eventId);
speakersCallLive.getValue().setEvent(event);

compositeDisposable.add(speakersCallRepository.createSpeakersCall(speakersCallLive.getValue())
.doOnSubscribe(disposable -> progress.setValue(true))
.doFinally(() -> progress.setValue(false))
.subscribe(var -> success.setValue(“Speakers Call Created Successfully”),
throwable -> error.setValue(ErrorUtils.getMessage(throwable))));
}
}

We will create a Fragment class to bind the UI elements to the Model class so that they can be processed by ViewModel. The creation form contains less fields therefore BaseBottomSheetFragment is used. In the onStart method, we are binding several LiveData objects like SpeakersCall, Progress, Error, Success to the UI and then observe changes in them. Using LiveData we don’t have to write extra code to handle screen rotation and to update UI when the binded object is modified.

public class CreateSpeakersCallFragment extends BaseBottomSheetFragment implements CreateSpeakersCallView {
@Override
public void onStart() {
super.onStart();
createSpeakersCallViewModel.getSpeakersCall().observe(this, this::showSpeakersCall);
createSpeakersCallViewModel.getProgress().observe(this, this::showProgress);
createSpeakersCallViewModel.getError().observe(this, this::showError);
createSpeakersCallViewModel.getSuccess().observe(this, this::onSuccess);
createSpeakersCallViewModel.initialize();
}
}

We will design the speaker create form using Two Way Data Binding to bind SpeakersCall object to the UI and use TextInputEditText to take user Input. Along with that we have used Time and Date pickers to make it easier to select Date and Time. For viewing the complete code, please refer here.

Thus, we are able to build a responsive, reactive UI using Android Architectural Components.

References

  1. Official documentation of Retrofit 2.x http://square.github.io/retrofit/
  2. Official documentation for RxJava 2.x https://github.com/ReactiveX/RxJava
  3. Official documentation for ViewModel https://developer.android.com/topic/libraries/architecture/viewmodel
  4. Codebase for Open Event Orga App https://github.com/fossasia/open-event-orga-app
Continue Reading

Implementing Scheduler Actions on Open Event Frontend

After the functionality to display scheduled sessions was added to Open Event Frontend, the read-only implementation of the scheduler had been completed. What was remaining now in the scheduler were the write actions, i.e., the sessions’ scheduling which event organizers do by deciding its timings, duration and venue.

First of all, these actions required the editable flag to be true for the fullcalendar plugin. This allowed the sessions displayed to be dragged and dropped. Once this was enabled, the next task was to embed data in each of the unscheduled sessions so that when they get dropped on the fullcalendar space, they get recognized by the calendar, which can place it at the appropriate location. For this functionality, they had to be jQuery UI draggables and contain an “event” data within them. This was accomplished by the following code:

this.$().draggable({
  zIndex         : 999,
  revert         : true,      // will cause the event to go back to its
  revertDuration : 0  //  original position after the drag
});

this.$().data('event', {
  title    : this.$().text().replace(/\s\s+/g, ' '), // use the element's text as the event title
  id       : this.$().attr('id'),
  serverId : this.get('session.id'),
  stick    : true, // maintain when user navigates (see docs on the renderEvent method)
  color    : this.get('session.track.color')
});

Here, “this” refers to each unscheduled session. Note that the session color is fetched via the corresponding session track. Once the unscheduled sessions contain enough relevant data and are of the right type (i.e, jQuery UI draggable type), they’re ready to be dropped on the fullcalendar space.

Now, when an unscheduled session is dropped on the fullcalendar space, fullcalendar’s eventReceive callback is triggered after its drop callback. In this callback, the code removes the session data from the unscheduled sessions’ list, so it disappears from there and gets stuck to the fullcalendar space. Then the code in the drop callback makes a PATCH request to Open Event Server with the relevant data, i.e, start and end times as well as microlocation. This updates the corresponding session on the server.

Similarly, another callback is generated when an event is resized, which means when its duration is changed. This again sends a corresponding session PATCH request to the server. Furthermore, the functionality to pop a scheduled event out of the calendar and add it back to the unscheduled sessions’ list is also implemented, just like in Eventyay version 1. For this, a cross button is implemented, which is embedded in each scheduled session. Clicking this pops the session out of the calendar and adds it back to the unscheduled sessions list. Again, a corresponding PATCH request is sent to the server.

After getting the response of such requests, a notification is displayed on the screen, which informs the users whether the action was successful or not. The main PATCH functionality is in a separate function which is called by different callbacks accordingly, so code reusability is increased:

updateSession(start, end, microlocationId, sessionId) {
    let payload = {
      data: {
        attributes: {
          'starts-at' : start ? start.toISOString() : null,
          'ends-at'   : end ? end.toISOString() : null
        },
        relationships: {
          microlocation: {
            data: {
              type : 'microlocation',
              id   : microlocationId
            }
          }
        },
        type : 'session',
        id   : sessionId
      }
    };

    let config = {
      skipDataTransform: true
    };
    return this.get('loader')
      .patch(`sessions/${sessionId}`, JSON.stringify(payload), config)
      .then(() => {
        this.get('notify').success('Changes have been made successfully');
      })
      .catch(reason => {
        this.set('error', reason);
        this.get('notify').error(`Error: ${reason}`);
      });
  },

This completes the scheduler implementation on Open Event Frontend. Here is how it looks in action:

scheduler actions.gif

Resources

Continue Reading

Open Event Server – Export Speakers as PDF File

FOSSASIA‘s Open Event Server is the REST API backend for the event management platform, Open Event. Here, the event organizers can create their events, add tickets for it and manage all aspects from the schedule to the speakers. Also, once he/she makes his event public, others can view it and buy tickets if interested.

The organizer can see all the speakers in a very detailed view in the event management dashboard. He can see the statuses of all the speakers. The possible statuses are pending, accepted, and rejected. He/she can take actions such as editing the speakers.

If the organizer wants to download the list of all the speakers as a PDF file, he or she can do it very easily by simply clicking on the Export As PDF button in the top right-hand corner.

Let us see how this is done on the server.

Server side – generating the Speakers PDF file

Here we will be using the pisa package which is used to convert from HTML to PDF. It is a html2pdf converter which uses ReportLab Toolkit, the HTML5lib and pyPdf. It supports HTML5 and CSS 2.1 (and some of CSS 3). It is completely written in pure Python so it is platform independent.

from xhtml2pdf import pisa<

We have a utility method create_save_pdf which creates and saves PDFs from HTML. It takes the following arguments:

  • pdf_data – This contains the HTML template which has to be converted to PDF.
  • key – This contains the file name
  • dir_path – This contains the directory

It returns the newly formed PDF file. The code is as follows:

def create_save_pdf(pdf_data, key, dir_path='/static/uploads/pdf/temp/'):
   filedir = current_app.config.get('BASE_DIR') + dir_path

   if not os.path.isdir(filedir):
       os.makedirs(filedir)

   filename = get_file_name() + '.pdf'
   dest = filedir + filename

   file = open(dest, "wb")
   pisa.CreatePDF(io.BytesIO(pdf_data.encode('utf-8')), file)
   file.close()

   uploaded_file = UploadedFile(dest, filename)
   upload_path = key.format(identifier=get_file_name())
   new_file = upload(uploaded_file, upload_path)
   # Removing old file created
   os.remove(dest)

   return new_file

The HTML file is formed using the render_template method of flask. This method takes the HTML template and its required variables as the arguments. In our case, we pass in ‘pdf/speakers_pdf.html’(template) and speakers. Here, speakers is the list of speakers to be included in the PDF file. In the template, we loop through each item of speakers. We print his name, email, list of its sessions, mobile, a short biography, organization, and position. All these fields form a row in the table. Hence, each speaker is a row in our PDF file.

The various columns are as follows:

<thead>
<tr>
   <th>
       {{ ("Name") }}
   </th>
   <th>
       {{ ("Email") }}
   </th>
   <th>
       {{ ("Sessions") }}
   </th>
   <th>
       {{ ("Mobile") }}
   </th>
   <th>
       {{ ("Short Biography") }}
   </th>
   <th>
       {{ ("Organisation") }}
   </th>
   <th>
       {{ ("Position") }}
   </th>
</tr>
</thead>

A snippet of the code which handles iterating over the speakers’ list and forming a row is as follows:

{% for speaker in speakers %}
   <tr class="padded" style="text-align:center; margin-top: 5px">
       <td>
           {% if speaker.name %}
               {{ speaker.name }}
           {% else %}
               {{ "-" }}
           {% endif %}
       </td>
       <td>
           {% if speaker.email %}
               {{ speaker.email }}
           {% else %}
               {{ "-" }}
           {% endif %}
       </td>
       <td>
           {% if speaker.sessions %}
               {% for session in speaker.sessions %}
                   {{ session.name }}<br>
               {% endfor %}
           {% else %}
               {{ "-" }}
           {% endif %}
       </td>
      …. So on
   </tr>
{% endfor %}

The full template can be found here.

Obtaining the Speakers PDF file:

Firstly, we have an API endpoint which starts the task on the server.

GET - /v1/events/{event_identifier}/export/speakers/pdf

Here, event_identifier is the unique ID of the event. This endpoint starts a celery task on the server to export the speakers of the event as a PDF file. It returns the URL of the task to get the status of the export task. A sample response is as follows:

{
  "task_url": "/v1/tasks/b7ca7088-876e-4c29-a0ee-b8029a64849a"
}

The user can go to the above-returned URL and check the status of his/her Celery task. If the task completed successfully he/she will get the download URL. The endpoint to check the status of the task is:

and the corresponding response from the server –

{
  "result": {
    "download_url": "/v1/events/1/exports/http://localhost/static/media/exports/1/zip/OGpMM0w2RH/event1.zip"
  },
  "state": "SUCCESS"
}

The file can be downloaded from the above-mentioned URL.

Resources

Continue Reading

Implementing Scheduled Sessions in Open Event Scheduler

Until recently, the Open Event Frontend version 2 didn’t have the functionality to display the already scheduled sessions of an event on the sessions scheduler. Displaying the already scheduled sessions is important so that the event organizer can always use the sessions scheduler as a draft and not worry about losing progress or data about scheduled sessions’ timings. Therefore, just like a list of unscheduled sessions was implemented for the scheduler, the provision for displaying scheduled sessions also had to be implemented.

The first step towards implementing this was to fetch the scheduled sessions’ details from Open Event Server. To perform this fetch, an appropriate filter was required. This filter should ideally ask the server to send only those sessions that are “scheduled”. Thus, scheduled sessions need to be defined as sessions which have a non-null value of its starts-at and ends-at fields. Also, few more details are required to be fetched for a clean display of scheduled sessions. First, the sessions’ speaker details should be included so that the speakers’ names can be displayed alongside the sessions. Also, the microlocations’ details need to be included so that each session is displayed according to its microlocation. For example, if a session is to be delivered in a place named ‘Lecture Hall A’, it should appear under the ‘Lecture Hall A’ microlocation column. Therefore, the filter goes as follows:

let scheduledFilterOptions = [
      {
        and: [
          {
            name : 'starts-at',
            op   : 'ne',
            val  : null
          },
          {
            name : 'ends-at',
            op   : 'ne',
            val  : null
          }
        ]
      }
    ];

 

After fetching the scheduled sessions’ details, they need to be delivered to the fulllcalendar code for displaying on the session scheduler. For that, the sessions need to be converted in a format which can be parsed by the fullcalendar add-on of emberJS. For example, fullcalendar calls microlocations as ‘resources’. Here is the format which fullcalendar understands:

{
        title      : `${session.title} | ${speakerNames.join(', ')}`,
        start      : session.startsAt.format('YYYY-MM-DDTHH:mm:SS'),
        end        : session.endsAt.format('YYYY-MM-DDTHH:mm:SS'),
        resourceId : session.microlocation.get('id'),
        color      : session.track.get('color'),
        serverId   : session.get('id') // id of the session on BE
}

 

Once the sessions are in the appropriate format, their data is sent to the fullcalendar template, which renders them on the screen:

Screen Shot 2018-08-21 at 8.20.27 PM.png

This completes the implementation of displaying the scheduled sessions of an event on the Open Event Scheduler.

Resources

Continue Reading
Close Menu
%d bloggers like this: