Integrating Orders API to Allow User Select Tickets for Placing Order in Open Event Frontend
In Open Event Frontend organizer has option to sell tickets for his event. Tickets which are available to public are listed in public page of event for users to buy. In this blog post we will learn how to integrate orders API and manage multiple tickets of varying quantity under an order.
For orders we mainly interact with three API endpoints.
- Orders API endpoint
- Attendees API endpoint
- Tickets API endpoint
Orders and attendees have one to many relationship and similarly orders and tickets also have one to many relationship. Every attendee is related to one ticket. In simple words one attendee has one ticket and one order can contain many tickets of different quantity each meant for different attendee.
// routes/public/index.js order: this.store.createRecord('order', { event : eventDetails, user : this.get('authManager.currentUser'), tickets : [], attendees : [] })
We need to create instance of order model to fill in data in that record. We do this in our routes folder of public route. Code snippet is given below.
We create a empty array for tickets and attendees so that we can add their record instances as relationship to order.
As given in screenshot we have a dropdown for each ticket to select quantity of each ticket. To allow user select quantity of a ticket we use #ui-dropdown component of ember-semantic-ui in our template. The code snippet of that is given below.
// templates/components/public/ticket-list.js {{#ui-dropdown class='compact selection' forceSelection=false onChange=(action 'updateOrder' ticket) as |execute mapper|}} {{input type='hidden' id=(concat ticket.id '_quantity') value=ticket.orderQuantity}} <i class="dropdown icon"></i>class="default text">0class="menu">class="item" data-value="{{map-value mapper 0}}">{{0}}{{#each (range ticket.minOrder ticket.maxOrder) as |count|}}class="item" data-value="{{map-value mapper count}}">{{count}}{{/each}} </div> {{/ui-dropdown}}
For every change in quantity of ticket selected we call a action named updateOrder. The code snippet for this action is given below.
// components/public/ticket-list.js updateOrder(ticket, count) { let order = this.get('order'); ticket.set('orderQuantity', count); order.set('amount', this.get('total')); if (count > 0) { order.tickets.addObject(ticket); } else { if (order.tickets.includes(ticket)) { order.tickets.removeObject(ticket); } } },
Here we can see that if quantity of selected ticket is more than 0 we add that ticket to our order otherwise we remove that ticket from the order if it already exists.
Once user selects his tickets for the order he/she can order the tickets. On clicking Order button we call placeOrder action. With help of this we add all the relations and finally send the order information to the server. Code snippet is given below.
// components/public/ticket-list.js placeOrder() { let order = this.get('order'); let event = order.get('event'); order.tickets.forEach(ticket => { let attendee = ticket.orderQuantity; let i; for (i = 0; i < attendee; i++) { order.attendees.addObject(this.store.createRecord('attendee', { firstname : 'John', lastname : 'Doe', email : 'johndoe@example.com', event, ticket })); } }); this.sendAction('save'); }
Here for each ticket placed under an order we create a dummy attendee related to ticket and event. And then we call save action to save all the models. Code snippet for save is given:
actions: { async save() { try { this.set('isLoading', true); let order = this.get('model.order'); let attendees = order.get('attendees'); await order.save(); attendees.forEach(async attendee => { await attendee.save(); }); await order.save() .then(order => { this.get('notify').success(this.get('l10n').t('Order created.')); this.transitionToRoute('orders.new', order.id); }); } catch (e) { this.get('notify').error(this.get('l10n').t('Oops something went wrong. Please try again')); } } }
Here we finally save all the models and transition to orders page to enable user fill the attendees details.
Resources:
- Orders API: https://open-event-api-dev.herokuapp.com/#orders
- Tickets API: https://open-event-api-dev.herokuapp.com/#tickets
- Attendees API: https://open-event-api-dev.herokuapp.com/#attendees
Modifying Tickets API in Open Event Server to Return Hidden Tickets Only for Organizers and Admins
This blog article will illustrate how we can modify the permissions settings for an API to enable different kind of responses to users with different level of permissions. In this article we will discuss these changes with respect to Tickets API.
Initially we had a query where we were returning only those tickets who were set to be visible by the admin. Query for this was:
class TicketList(ResourceList): """ List Tickets based on different params """ def before_get(self, args, view_kwargs): """ before get method to get the resource id for assigning schema :param args: :param view_kwargs: :return: """ if view_kwargs.get('ticket_tag_id') or view_kwargs.get('access_code_id') or view_kwargs.get('order_identifier'): self.schema = TicketSchemaPublic def query(self, view_kwargs): """ query method for resource list :param view_kwargs: :return: """ query_ = self.session.query(Ticket).filter_by(is_hidden=False)
Problem with this query was that this returned same response irrespective of who is logged in. Hence even the organizers were not able to modify hidden tickets because they were not returned by server.
Solution to this problem was to provide hidden tickets only to those who are organizer or are admin/super admins. For this we used the JWT token that was being sent from frontend in request headers for each authenticated request that was being made from frontend.
We modified the code to something like this:
class TicketList(ResourceList): """ List Tickets based on different params """ def before_get(self, args, view_kwargs): """ before get method to get the resource id for assigning schema :param args: :param view_kwargs: :return: """ if view_kwargs.get('ticket_tag_id') or view_kwargs.get('access_code_id') or view_kwargs.get('order_identifier'): self.schema = TicketSchemaPublic def query(self, view_kwargs): """ query method for resource list :param view_kwargs: :return: """ if 'Authorization' in request.headers: _jwt_required(current_app.config['JWT_DEFAULT_REALM']) if current_user.is_super_admin or current_user.is_admin: query_ = self.session.query(Ticket) elif view_kwargs.get('event_id') and has_access('is_organizer', event_id=view_kwargs['event_id']): query_ = self.session.query(Ticket) else: query_ = self.session.query(Ticket).filter_by(is_hidden=False) else: query_ = self.session.query(Ticket).filter_by(is_hidden=False)
Here we added some conditions which were used to check permission level of logged in user. After picking JWT token from request headers we check if the user was admin or super_admin, then we return all the tickets without any condition. Then we also check if the logged in user was organizer of event then also we send all the tickets without any conditions.
However if request comes from unauthenticated users (without valid token) or users with normal privileges, then we returned tickets whose isHidden property was set to False. The functions such as is_organizer and is_super_admin acted as helpers as they were imported from other helper files where they were defined.
Resources
- Open Event Server: Link to PR
- Open Event API Docs
- Open Event server ticketsAPI
How We Implement Custom Forms for Sessions and Speaker Form in Open Event Frontend
In this blog we will see the implementation of custom form for session and speakers form. Since every event organiser requires different fields required to be filled by speakers for their sessions, for ex. Some event organiser may need GitHub profile whereas other may need LinkedIn profile so, we implemented custom form for session and speaker form in Open Event Frontend so that every organiser can choose fields he/she requires to be filled by his speakers who are filling their proposal. Custom form allows following features:
- Allows organiser to choose whether he wants a particular field or not.
- Organiser can make a field compulsory, so that no speaker can submit his proposal without filling that field.
So, to get started we define our getCustomForm() method in mixins and it looks something like this:
import Mixin from '@ember/object/mixin'; import MutableArray from '@ember/array/mutable'; export default Mixin.create(MutableArray, { getCustomForm(parent) { return [ this.store.createRecord('custom-form', { fieldIdentifier : 'title', form : 'session', type : 'text', isRequired : true, isIncluded : true, isFixed : true, event : parent }), this.store.createRecord('custom-form', { fieldIdentifier : 'subtitle', form : 'session', type : 'text', isRequired : false, isIncluded : false, event : parent }), this.store.createRecord('custom-form', { fieldIdentifier : 'shortAbstract', form : 'session', type : 'text', isRequired : false, isIncluded : true, event : parent }), ...
Here we define all the possible fields with their properties. Every field has different properties that enables us to identify whether to choose or reject them. So to enable this these fields to be chosen by organiser we need to feed them into model.
In our session-speakers-step.js in components we assign these fields to our model thorugh this:
didInsertElement() { if (this.get('data.event.customForms')&&!his.get('data.event.customForms.length')) { this.set('data.event.customForms', this.getCustomForm(this.get('data.event'))); } }
This hook gets called when our component is rendered and custom form fields gets assigned to data.event.customForms in our model.
In our handlebars template we create sliders to enable organiser to choose whether he wants to enable a field or not.
In our template logic we write a loop that renders these sliders for each fields and manipulate them as organiser slides any slider for a field. Let’s take a look at the template code block:
<tbody> {{#each customForm.session as |field|}} <tr class="{{if field.isIncluded 'positive'}}"> <td class="{{if device.isMobile 'center' 'right'}} aligned"> <label class="{{if field.isFixed 'required'}}"> {{field.name}} </label> </td> <td class="center aligned"> {{ui-checkbox class='slider' checked=field.isIncluded disabled=field.isFixed onChange=(action (mut field.isIncluded)) label=(if device.isMobile (t 'Include'))}} </td> <td class="center aligned"> {{ui-checkbox class='slider' checked=field.isRequired disabled=field.isFixed onChange=(action (mut field.isRequired)) label=(if device.isMobile (t 'Require'))}} </td> </tr> {{/each}} </tbody>
Every slider has a action attached to it that manipulates a property of the custom for and on proceeding it saves the custom form model to server.
Later on when rendering for for speaker we fetch details from server about which fields are required or not.
Resources
How RSVP Handles Promises in Open Event Frontend
This blog post illustrates how to manage multiple promises simultaneously using a library known as RSVP in Open Event frontend.
What are Promises?
Promises are used to manage synchronous calls in javascript. Promises represent a value/object that may not be available yet but will become available in near future. To quote from MDN web docs:
The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.
What about RSVP?
Rsvp is a lightweight library used to organize asynchronous code. Rsvp provides several ways to handle promises and their responses. A very simple promise implementation using rsvp looks something like this.
var RSVP = require('rsvp'); var promise = new RSVP.Promise(function(resolve, reject) { // succeed resolve(value); // or reject reject(error); }); promise.then(function(value) { // success }).catch(function(error) { // failure });
It’s simple, right? So, what it is doing is after it defines a promise it assumes two possible states of a promise which are resolve or reject and after promise has completed it executes the respective function.
Use in Open Event Frontend?
Almost all calls to open event server APIs are done asynchronously. One of the most significant use of rsvp comes when handling multiple promises in frontend and we want all of them to be evaluated at together. Unlike normal promises where each promises resolved or rejected individually, rsvp provides a promise.all() method which accepts array of promises and evaluates all at once. It then calls resolve or reject based on the status of all promises. A typical example where we use promise.all() is given here.
import Controller from '@ember/controller'; import RSVP from 'rsvp'; import EventWizardMixin from 'open-event-frontend/mixins/event-wizard'; export default Controller.extend(EventWizardMixin, { actions: { save() { this.set('isLoading', true); this.get('model.data.event').save() .then(data => { let promises = []; promises.push(this.get('model.data.event.tickets').toArray().map(ticket => ticket.save())); promises.push(this.get('model.data.event.socialLinks').toArray().map(link => link.save())); if (this.get('model.data.event.copyright.licence')) { let copyright = this.setRelationship(this.get('model.data.event.copyright.content'), data); promises.push(copyright.save()); } if (this.get('model.data.event.tax.name')) { let tax = this.setRelationship(this.get('model.data.event.tax.content'), data); if (this.get('model.event.isTaxEnabled')) { promises.push(tax.save()); } else { promises.push(tax.destroyRecord()); } } RSVP.Promise.all(promises) .then(() => { this.set('isLoading', false); this.get('notify').success(this.get('l10n').t('Your event has been saved')); this.transitionToRoute('events.view.index', data.id); }, function() { this.get('notify').error(this.get('l10n').t('Oops something went wrong. Please try again')); }); }) .catch(() => { this.set('isLoading', false); this.get('notify').error(this.get('l10n').t('Oops something went wrong. Please try again')); }); }, }
Here we made array of promises and then pushed each of the promises to it. Then at the end used this array of promises in RSVP.promise.all() which then evaluated it based on success or failure of the promises.
Resources:
- RSVP Official Docs, Inch CI: RSVP official documentation
- Documentation on Promise: MDN web docs: Promises (MDN Docs)
Different Text Color On Each Line In Badgeyay
In this blog post I am going to explain about how to create different text color for each line in badges generation in Badgeyay. As the system now has option for different badge size and paper size, currently the system sets same color for each line by mutating the fill parameter in the SVG. The main challenge in mutating the SVG parameter for each badge is the Id. The ID identifies the element, in our case text, and gives access to iterate the SVG through libraries like lxml. So for implementing this feature we first need to manipulate the SVG and assign id’s to the text tag so that it can be easily manipulated through the algorithm.
Procedure
- Manipulating the text tag in SVG and assigning a proper ID according to the logic for iteration in the function.
<text
id=“Person_color_1_1” ….> Person_1_1 </text> |
The id of the person in first badge and first line is represented as Person_color_1_1, where the first number denotes the number of badge and second number denotes the line number.
- Creating a class for the dimensions of the badges
class Dimen(object): def __init__(self, badges, badgeSize, paperSize): self.badges = badges self.badgeSize = badgeSize self.paperSize = paperSize |
- Creating an initialiser function that stores the dimension objects
badge_config = {} def init_dimen(): paper_sizes = [‘A2’, ‘A3’, ‘A4’] for paper in paper_sizes: if paper == ‘A2’: badge_config.__setitem__(paper, {‘4×3’: Dimen(18, ‘4×3’, paper)}) badge_config[paper][‘4.5×4’] = Dimen(15, ‘4.5×4’, paper) elif paper == ‘A3’: badge_config.__setitem__(paper, {‘4×3’: Dimen(8, ‘4×3’, paper)}) badge_config[paper][‘4.5×4’] = Dimen(6, ‘4.5×4’, paper) elif paper == ‘A4’: badge_config.__setitem__(paper, {‘4×3’: Dimen(6, ‘4×3’, paper)}) badge_config[paper][‘4.5×4’] = Dimen(2, ‘4.5×4’, paper) |
- Selecting the dimension config based on the parameters passed in the function.
dimensions = badge_config[paper_size][badge_size] |
- Looping criteria is to loop through the number of badges mentioned in the dimension config and through the number of lines which will be five.
for idx in range(1, dimensions.badges + 1): for row in range(1, 6): |
- Selecting the text element with the ID as provided above.
_id = ‘Person_color_{}_{}’.format(idx, row) path = element.xpath((“//*[@id='{}’]”).format(_id))[0] |
- Fill the text color argument of the selected object by changing the value of fill.
style_detail[6] = “fill:” + str(fill[row]) |
That’s it and now when the loop runs each line will have its individual color as passed in the function. The choice of color is passed as the list named fill.
Resources
- Pull request for the same – https://github.com/fossasia/badgeyay/pull/1498
- LXML documentation – https://lxml.de/
- Parsing the SVG – https://stackoverflow.com/questions/15857818/python-svg-parser
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.
- First we get a suitable filename for a image file using get_file_name() function.
- After getting filename, we check if the url provided by user is a valid url or not.
- If the url is invalid then we use the default system image as the image of that particular event topic.
- 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.
- After successful reading of image we upload the image to event_topic directory in static directory of the project.
- 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
- Documentation | Input / Output in python: https://docs.python.org/3/tutorial/inputoutput.html
- Documentation | Uploading files in Flask: http://flask.pocoo.org/docs/1.0/patterns/fileuploads/
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.
- First of all, we are provide the two fields in this Schema, which are id and name.
- 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.
- 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.
- CustomSystemRoleList inherits ResourceList which will give us list of all the custom system roles in the whole system.
- CustomSystemRoleList has a decorators attribute which gives the permission of POST request to only admins of the system.
- CustomSystemRoleDetail inherits ResourceDetail which will give the details of a CustomSystemRole object by id.
- 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
- Documentation | Marshmallow : https://marshmallow-jsonapi.readthedocs.io/en/latest/
- Documentation | Flask Rest JSONAPI : http://flask-rest-jsonapi.readthedocs.io/en/latest/
- Documentation | Roles in Open Event Server: https://github.com/fossasia/open-event-server/blob/development/docs/general/roles.md
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.
- First of all, we are provide the four fields in this Schema, which are id, panel_name, role_id and can_access.
- 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.
- 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.
- Next attribute role_id should be of integer type as it will tell us that to which role current panel is concerning.
- 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.
- 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.
- PanelPermissionList inherits ResourceList which will give us list of all the custom system roles in the whole system.
- PanelPermissionList has a decorators attribute which gives the permission of both GET and POST requests to only admins of the system.
- The POST request of PanelPermissionList API requires the relationship of role.
- PanelPermissionDetail inherits ResourceDetail which will give the details of a Panel Permission object by id.
- 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
- Documentation | Marshmallow : https://marshmallow-jsonapi.readthedocs.io/en/latest/
- Documentation | Flask Rest JSONAPI : http://flask-rest-jsonapi.readthedocs.io/en/latest/
- Documentation | Roles in Open Event Server: https://github.com/fossasia/open-event-server/blob/development/docs/general/roles.md
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:
Resources
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.
You must be logged in to post a comment.