Option to add description to an image in the Phimpme Android app

In the Phimpme Android application, users can perform various operations on images such as editing an image, sharing an image, moving the image to another folder, printing a pdf version of the image and many more. However, another important functionality that has been implemented is the option to add some description to the image. So in this blog post, I will be discussing how we achieved the functionality to add a description to any image.

Step 1

First, we need to add an option in the overflow menu to add description for the image being viewed. The option to add description to an image has been achieved by adding the following lines of code in the  menu_view_pager.xml file.

<item
  android:id=“@+id/action_description”
  android:title=“Description”
  app:showAsAction=“never” />

Step 2

Now after the user chooses the option to add description to the image, an alertdialog with an edittext would be displayed to the user to enter the description. The dialog box with edittext has been implemented with the following lines of XML code.

<android.support.v7.widget.CardView
  android:layout_width=“match_parent”
  android:layout_height=“wrap_content”
  app:cardCornerRadius=“2dp”
  android:id=“@+id/description_dialog_card”>
  <ScrollView
      android:layout_width=“match_parent”
      android:layout_height=“match_parent”>
      <LinearLayout
          android:layout_width=“match_parent”
          android:layout_height=“wrap_content”
          android:orientation=“vertical”>
          <TextView
              android:id=“@+id/description_dialog_title”
              android:layout_width=“match_parent”
              android:textColor=“@color/md_dark_primary_text”
              android:layout_height=“wrap_content”
              android:background=“@color/md_dark_appbar”
              android:padding=“24dp”
              android:text=“@string/type_description”
              android:textSize=“18sp”
              android:textStyle=“bold” />
          <LinearLayout
              android:id=“@+id/rl_description_dialog”
              android:layout_width=“match_parent”
              android:layout_height=“wrap_content”
              android:orientation=“horizontal”
              android:padding=“15dp”>
              <EditText
                  android:id=“@+id/description_edittxt”
                  android:layout_width=“fill_parent”
                  android:layout_height=“wrap_content”
                  android:padding=“15dp”
                  android:hint=“@string/description_hint”
                  android:textColorHint=“@color/grey”
                  android:layout_margin=“20dp”
                  android:gravity=“top|left”
                  android:inputType=“textCapSentences|textMultiLine”
                  android:maxLength=“2000”
                  android:maxLines=“4”
                  android:selectAllOnFocus=“true”/>
              <ImageButton
                  android:layout_width=“@dimen/mic_image”
                  android:layout_height=“@dimen/mic_image”
                  android:layout_alignRight=“@+id/description_edittxt”
                  app2:srcCompat=“@drawable/ic_mic_black”
                  android:layout_gravity=“center”
                  android:background=“@color/transparent”
                  android:paddingEnd=“10dp”
                  android:paddingTop=“12dp”
                  android:id=“@+id/voice_input”/>
          </LinearLayout>
      </LinearLayout>
  </ScrollView>
</android.support.v7.widget.CardView>

The screenshot of the dialog to enter description has been provided below.

Step 3

Now after retrieving the description added by the user, the description is saved in the realm database using the realm model object ImageDescModel which will contain the path of the image and the description added as its attributes. The path of the image has been used as the primary key for the description table. The realm model class used for the above-mentioned operation is described below.

public class ImageDescModel extends RealmObject {
  @PrimaryKey
  private String path;
  private String desc;

  public ImageDescModel() {
  }

  public ImageDescModel(String path, String title) {
      this.path = path;
      this.desc = title;
  }

  public String getId() {
      return path;
  }

  public void setId(String path) {
      this.path = path;
  }

  public String getTitle() {
      return desc;
  }

  public void setTitle(String description) {
      this.desc = description;
  }
}

This is how we have implemented the functionality to add description to images in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository listed in the resource section below.

Resources

1.Realm for Android – https://realm.io/blog/realm-for-android/

2.Github-Phimpme Android Repository – https://github.com/fossasia/phimpme-android/

3.Working with realm tutorial – https://www.androidhive.info/2016/05/android-working-with-realm-database-replacing-sqlite-core-data/

Continue ReadingOption to add description to an image in the Phimpme Android app

Creating API to Retrieve Images in SUSI.AI Server

SUSI Server needed to have an API where users can upload and store images. This images will be useful in setting custom theme for the botbuilder or for chat.susi.ai. User can upload image from their systems instead of pasting the link of an image stored in cloud. Also we need an API to retrieve the stored image given its full path. This blog explains how the SUSI Server returns the image stored upon requesting.

Understanding where the image is stored

The images uploaded to the SUSI Server is stored in its local storage. The local storage is where all the json files and other data are stored. We store the images inside “image_uploads” folder. In the get image API, the client will provide the full path of the image i.e the user UUID and the image’s full name. Then we need to fetch this image from the local storage and return it.

Retrieving the image from local storage

The Retrieving image API has the endpoint “/cms/getImage.png”. The servlet file is “GetImageServlet.java”. The client has to send the full path of the image in the parameter “image”. A sample GET request is “http://127.0.0.1:4000/cms/getImage.png?image=963e84467b92c0916b27d157b1d45328/1529692996805_susi icon.png”.

We need to retrieve the given image from the correct path.

Query post = RemoteAccess.evaluate(request);
String image_path = post.get("image","");
File imageFile = new File(DAO.data_dir  + File.separator + "image_uploads" + File.separator+ image_path);
if (!imageFile.exists()) {response.sendError(503, "image does not exist"); return;}
String file = image_path.substring(image_path.indexOf("/")+1);

 

Writing the image in Byte Stream

Now that we have the image file, we need to write that image in a Byte array output stream. We read the pixels data from the image using “FileInputStream” method.

ByteArrayOutputStream data = new ByteArrayOutputStream();
byte[] b = new byte[2048];
InputStream is = new BufferedInputStream(new FileInputStream(imageFile));
int c;
try {
while ((c = is.read(b)) >  0) {data.write(b, 0, c);}
} catch (IOException e) {}

 

Returning the image to the client

Now that we have the image data in the Byte array, we can send that data to the client. Before that we have to set the response type. We set the response type accordingly if the image is png, gif, or jpg. For other types, we the response type as “octet-stream”. Finally we attach the image in the response object and send it.

if (file.endsWith(".png") || (file.length() == 0 && request.getServletPath().endsWith(".png"))) post.setResponse(response, "image/png");
else if (file.endsWith(".gif") || (file.length() == 0 && request.getServletPath().endsWith(".gif"))) post.setResponse(response, "image/gif");
else if (file.endsWith(".jpg") || file.endsWith(".jpeg") || (file.length() == 0 && request.getServletPath().endsWith(".jpg"))) post.setResponse(response, "image/jpeg");
else post.setResponse(response, "application/octet-stream");

ServletOutputStream sos = response.getOutputStream();
sos.write(data.toByteArray());
post.finalize();

 

Result:

Resources

 

Continue ReadingCreating API to Retrieve Images in SUSI.AI Server

Option to rename albums in the Phimpme Android Application

In the Phimpme Android application, users can perform various operations on the albums available such as creating a zip file of the album, hiding an album and many more. However, one another useful functionality that has been added to the Phimpme Android application is the option to rename albums. So in this post, I will be discussing the implementation of the renaming functionality.

Step 1

First we need to add an option in the overflow menu to rename an album while we long-select an album or we are viewing photos inside a particular album. The option in the overflow menu can be added by integrating the following lines of code in the menu_albums.xml file(this file contains the xml code for overflow menu options provided for albums).

<item
android:id=”@+id/renameAlbum”
android:title=”@string/rename”
app:showAsAction=”never” />

Step 2

Now after the user selects the option in the overflow menu to rename the current viewing album, an alertdialog with an edittext would be displayed to the user to enter the new name for the album. The alertdialog containing the edittext has been implemented with the following lines of code.

<android.support.v7.widget.CardView
xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/vn.mbm.phimp.me”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
app:cardCornerRadius=”2dp”
android:id=”@+id/dialog_chose_provider_title”>
<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:orientation=”vertical”>
<TextView
android:id=”@+id/rename_title”
android:layout_width=”match_parent”
android:textColor=”@color/md_dark_primary_text”
android:layout_height=”wrap_content”
android:background=”@color/md_dark_appbar”
android:padding=”24dp”
android:text=”@string/rename_photo_action”
android:textSize=”18sp”
android:textStyle=”bold” />
<RelativeLayout
android:id=”@+id/container_edit_text”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”
android:padding=”24dp”/>
</LinearLayout>
</android.support.v7.widget.CardView>

The screenshot of the dialog to enter the new name is provided below.

Step 3

Finally, after retrieving the new name for the album entered by the user, the renameAlbum method is invoked by passing in the application context and new name as the parameters. Now inside the renameAlbum method, a new file path is created by the use of the getAlbumPathRenamed method of the StringUtils class and the current path of the album is changed to the new path generated earlier. Similarly path of each media inside the album is changed accordingly by the use of the method getPhotoPathRenamedAlbumChange of the StrinUtils class by looping through the media objects one by one. The lines of code implemented to perform the above-mentioned operations are provided below.

public boolean renameAlbum(final Context context, String newName) {
 found_id_album = false;
 boolean success;
 File newDir = new File(StringUtils.getAlbumPathRenamed(getPath(), newName));
 File oldDir = new File(getPath());
 success = oldDir.renameTo(newDir);
 if(success) {
    for (final Media m : media) {
       File from = new File(m.getPath());
       File to = new File(StringUtils.getPhotoPathRenamedAlbumChange(m.getPath(), newName));
       scanFile(context, new String[]{ from.getAbsolutePath() });
       scanFile(context, new String[]{ to.getAbsolutePath() }, new MediaScannerConnection.OnScanCompletedListener() {
          @Override
          public void onScanCompleted(String s, Uri uri) {
             if (!found_id_album) {
                id = MediaStoreProvider.getAlbumId(context, s);
                found_id_album = true;
             }
             Log.d(s, “onScanCompleted: “+s);
             m.setPath(s); m.setUri(uri.toString());
          }
       });
    }
    path = newDir.getAbsolutePath();
    name = newName;
 }
 return success;
}

This is how we have implemented the functionality to rename particular albums in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository listed in the resource section below.

Resources

1.Android Developer Guide – https://developer.android.com/reference/android/media/MediaScannerConnection

2.Github-Phimpme Android Repository – https://github.com/fossasia/phimpme-android/

3.Media Scanner tutorial –https://stackoverflow.com/questions/13270789/how-to-run-media-scanner-in-android

Continue ReadingOption to rename albums in the Phimpme Android Application

Creating API to Upload Images in SUSI.AI Server

SUSI Server needed to have an API where users can upload and store images. This images will be useful in setting custom theme for the botbuilder or for chat.susi.ai. User can upload image from their systems instead of pasting the link of an image stored in cloud. This blog explains how the SUSI Server stores the images in its local storage.

Creating Upload Image Service

UploadImageService.java is the file which creates the API to upload image. After creating the UploadImageService class in this file, we need to include the class in SusiServer.java to enable the API:

// add services
  services = new Class[]{
...
UploadImageService.class

Restricting access rights and giving API endpoint name

The Upload Image API should be available only to logged in users and not to anonymous users. Therefore we need to set the base user role to “USER”.  Also we need to set the name of the API endpoint. Lets set it to “/cms/uploadImage.json”.

@Override
public UserRole getMinimalUserRole() {
return UserRole.USER;
}

@Override
public String getAPIPath() {
return "/cms/uploadImage.json";
}

 

Creating API to accept the Post request

We need to accept the image uploaded by the client in a post request. The post request will contain the following parameters:

  • access_token – used to verify identity of the user and get their UUID
  • image_name – the name of the image
  • image (the image file) – the actual image file

To accept the post request on this route, we define the following function:

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("Access-Control-Allow-Origin", "*"); // enable CORS
JSONObject json = new JSONObject();
Part imagePart = req.getPart("image");
if (req.getParameter("access_token") != null) {
if (imagePart == null) {
json.put("accepted", false);
json.put("message", "Image not given");
} else {
// save image

 

Getting the correct storage location

We are saving the image in the local storage of the server, i.e inside the “data” folder. Inside it, we store inside the “image_uploads” folder. The image path and name has three parts:

  • User’s UUID as the sub folder’s name
  • System time in milliseconds as the first part of the image’s name
  • The image’s name given by the user as the second part of the image’s name

String image_name = req.getParameter("image_name");
ClientCredential credential = new ClientCredential(ClientCredential.Type.access_token, req.getParameter("access_token"));
Authentication authentication = DAO.getAuthentication(credential);
ClientIdentity identity = authentication.getIdentity();
String userId = identity.getUuid();
String imagePath = DAO.data_dir  + File.separator + "image_uploads" + File.separator + userId;

 

Saving the image

After we have formed the location of the image to be stored, we need to actually write that file to the local storage. Here is the code snippet which does that:

// Reading content for image
Image image = ImageIO.read(imagePartContent);
BufferedImage bi = this.toBufferedImage(image);
// Checks if images directory exists or not. If not then create one
if (!Files.exists(Paths.get(imagePath))) new File(imagePath).mkdirs();
String new_image_name = System.currentTimeMillis() + "_" + image_name;
File p = new File(imagePath + File.separator + new_image_name);
if (p.exists()) p.delete();
ImageIO.write(bi, "jpg", new File(imagePath + File.separator + new_image_name));

 

Thus the image gets stored in the local storage inside of the folder named by user’s UUID

Here, the number 963e84467b92c0916b27d157b1d45328 is the user UUID of the user and 1529692996805 is generated by getting the System time in milliseconds. This helps to differentiate between images of same name uploaded by the same user.

Resources

Continue ReadingCreating API to Upload Images in SUSI.AI Server

Migrating Event Ratings of Open Event with Stored Procedures

Many developers know about procedural languages and have used them in some form or another, but this is really an unpopular tool, despite its power. There are many advantages (and few disadvantages) of these languages, which we will learn about soon. Having a right amount of database-stored procedure code with the help of these languages can really enhance the speed and responsiveness of an application. This article will teach us how procedural languages can be utilized in database management and how they were used recently for a bug fix in Open Event Server.

PostgreSQL, like any other powerful, relational database management system (RDBMS), provides the functionality to create and use stored procedures. Essentially, a stored procedure is database logic code which is saved on the database server. This code can be executed directly in the database, and can (and is!) often used to shift business logic from the application layer of a software to the database layer. This simple shift often has many advantages – including faster execution (as code executes at a lower stack level) and better security. When firing database queries from the application layer (i.e., the code that programmers write for storing programmable objects, performing business logic and so on), it often happens that parameters from the programming language itself are passed in to SQL, which then generates a complete SQL query. For example, here’s how a novice query might look like:

import psycopg2
conn = psycopg2.connect(dbname="oevent", user="john", password="start")
cur = conn.cursor()
name = "Sam"
cur.execute("SELECT * FROM users WHERE name='%s'" % name)  # DANGEROUS!

This is an extremely “exposed” code that can be exploited for malicious access, with a technique called SQL injection. This technique essentially “injects” malicious code via these passed parameters, like the variable name mentioned in the above code. With having stored procedures for business logic, there is no room for SQL injection. They solve this problem by writing the query beforehand, and having the parameterized data as a different entity. The pre-processed query within the corresponding stored procedure now looks like

SELECT * FROM users WHERE name=?

 

The database driver sends the name of this stored procedure (or, in standard parameterised queries, just the query text itself) and a list of parameters, as distinct separate entities in the protocol. More details on how stored procedures enhance security can be found here.

After learning so much about the advantages of stored procedures (which are enabled by procedural languages), let’s write one! Postgres supports multiple languages for writing stored procedures; here we will use PL/pgSQL, which is the most popular choice for Postgres. This procedural language, inspired (heavily) by Oracle’s PL/SQL language, looks very similar to SQL. To use this procedural language, we have to first install it. In Postgres, procedural languages are installed per-database, not server-wide. We can use the popular Postgres client psql for this purpose, or simply the createlang command on the command line:

$ createlang plpgsql yourdb

 

Now let’s create a simple procedure that prints the corresponding grades for the event ratings. In the psql shell, type the following:

CREATE OR REPLACE FUNCTION grade(rating NUMERIC) RETURNS TEXT AS
$$
BEGIN
IF rating < 1 THEN
RETURN 'D';
ELSIF rating < 2 THEN
RETURN 'C';
ELSIF rating < 3 THEN
RETURN 'B';
ELSIF rating < 4 THEN
RETURN 'A';
ELSE
RETURN 'A+';
END IF;
END;
$$ STRICT LANGUAGE plpgsql IMMUTABLE;

 

The first line defines the function signature – its name, parameters’ type and return type. The line specifies which procedural language to use. The IMMUTABLE keyword specifies that the stored procedure – this function, cannot modify the database by itself. The STRICT part says that the function should always return null when any of the arguments are null. So, the function is not executed when there are null arguments, rather a null result is assumed automatically. Finally, everything within the $$ quotes is the logic of our function, in PL/pgSQL syntax. As we can see, the syntax is almost identical to that of plain SQL.

Let us now create some dummy data to test the above stored procedure. In the same psql shell, type the following:

CREATE TABLE ratings(id INTEGER, rating NUMERIC);
INSERT INTO ratings VALUES (1, 2.3), (2, 0.5), (3, 1.7), (4, 4.5), (5, 3.3);

 

The ratings table now looks as follows:

yourdb=# SELECT * FROM ratings;
id | rating
----+--------
1 |    2.3
2 |    0.5
3 |    1.7
4 |    4.5
5 |    3.3
(5 rows)

To test our stored procedure, we fire the following query:

SELECT id, grade(rating) FROM ratings;

The result is:

id | grade
----+-------
1 | B
2 | D
3 | C
4 | A+
5 | A
(5 rows)

Thus, our stored procedure works perfectly! This shows how you can write code for your application in the database itself, the code which would otherwise be present in the application layer. For writing more complex stored procedures, you can always consult the excellent PostgreSQL documentation on this topic!

PL/pgSQL turned out to be especially useful for a recent bug patch in Open Event Server. The rating attribute of event feedbacks was initially (and incorrectly) of String type. When converting it to Float type, I had migrated the schema, but the migration of existing data was nontrivial. Since the earlier ratings were of type String, we weren’t really sure whether the values in the rating column will all contain numbers. Furthermore, we also wanted to round those numbers to the “nearest 0.5”, so that ratings like 4.4 and 2.2 get converted to more conventional values like 4.5 and 2.0 respectively. To accomplish this, I had to add a stored procedure in the related migration file. Here’s how it looked:

class ReplaceableObject(object):
    def __init__(self, name, sqltext):
        self.name = name
        self.sqltext = sqltext

update_rating_func = ReplaceableObject(
    "update_rating(rating text)",
    """
    RETURNS text AS $$
    DECLARE
        r NUMERIC;
    BEGIN
        r = cast(rating as NUMERIC);
        IF r < 0 OR r > 5 THEN
            RETURN '0';
        END IF;
        r = round(r*2) / 2;
        RETURN cast(r as VARCHAR);
    EXCEPTION WHEN invalid_text_representation THEN
        RETURN '0';
    END;
    $$
    STRICT
    LANGUAGE plpgsql IMMUTABLE;
    """)

We created a simple class ReplaceableObject so that the name of the function is, sort of, a separate attribute from its definition, as required by the migration library alembic. Once the stored procedure was defined as above, the upgrade() function of the migration file was modified accordingly:

def upgrade():
    op.create_or_replace_sp(update_rating_func)
    op.execute("UPDATE feedback SET rating=update_rating(rating)")
    op.execute("DROP FUNCTION update_rating(text)")

    op.alter_column('feedback', 'rating',
        existing_type=sa.VARCHAR(),
        type_=sa.Float(),
        existing_nullable=False,
        postgresql_using='rating::double precision')

Notice how the UPDATE query calls our stored procedure update_rating() to make existing ratings of the form we desire. Once that is done, the procedure is no longer needed, so it is DROP-ed and then, finally, the type is changed to Float. So indeed, procedural languages are used for real!

Also, what’s more exciting is that one can write Postgres stored procedures in Python itself! Python is among the four procedural languages supported by PostgreSQL, the other two being Perl and Tcl. Here’s a simple Python example of the grading code we wrote earlier:

CREATE OR REPLACE FUNCTION grade(rating NUMERIC) RETURNS TEXT AS
$$
    if rating < 1:
        return 'D'
    elif rating < 2:
        return 'C'
    elif rating < 3:
        return 'B'
    elif rating < 4:
        return 'A'
    else
        return 'A+'
$$ STRICT LANGUAGE plpython3u IMMUTABLE;

This should work identical to the PL/pgSQL equivalent code above. But don’t forget to change the command for installing the procedural language, for Python it looks like:

$ createlang plpython3u yourdb

Needless to say, the 3 here stands for Python3.

Even with all this flexibility and power, one must note that procedural languages are not designed to replace programming languages. Procedural code can easily become difficult to maintain, and one often needs superuser privileges to create stored procedures. Moreover, portability is a mess with them. That is especially true when moving from one database system (like Postgres) to another (like Microsoft SQL Server). Therefore, for optimum application performance and code maintainability, ensure that there is a healthy balance between your business logic residing in stored procedures and the application layer.

This post covered a lot of information about code residing in the database and its use cases. If you want to know more about best practices involving separation of business logic, please check out this thread on StackExchange’s software engineering website.

Resources

Continue ReadingMigrating Event Ratings of Open Event with Stored Procedures

Implementing Event Average Rating with SQLAlchemy

While implementing Open Event Server version 2, we decided to have a better way of ranking events by their quality. To define the “quality” of events, the programmers decided to accumulate the feedbacks of specific events and take the average of the ratings involved. Thus, the average rating of an event proves to be a good (enough) measure of its quality. While there are many ways to implement aggregate relationships in an app, here I demonstrate a rather modern methodology which insists on storing such aggregates once they’re computed.

Since there is always a space-time/computation tradeoff in software development, this task was no exception. At first, the straightforward idea that came to my mind was to query the Postgres database every time a request for average rating was made. This sounds simple, but with hundreds of events stored on a server, and potentially thousands of users querying for events, this seemed to be a computationally expensive approach. It was costly because the average rating aggregate would be computed for each request, and there could potentially be thousands of such concurrent requests. Therefore, a better idea is to compute the aggregate once, store it in the database (compromising space in the tradeoff mentioned above, but saving a large amount of computation at the same time), and update only when a change is made. In our specific case, the update should happen only when a new rating is added, a rating is deleted or an existing rating is modified. Since the advantages outnumbered the disadvantages, this was the strategy to be implemented.

The first step in implementing average rating was to modify the database model of events accordingly. For this, I performed the necessary imports in the events’ database model file:

from sqlalchemy_utils import aggregated
from app.models.feedback import Feedback

Now comes the tricky part. We want an average_rating column in the events table, that contains the mean rating of events. The values in this column should be updated every time a change is made to the feedbacks table. To perform this sort of functionality, the best, raw tool is a Postgres trigger. A trigger should be created that is fired after every update to the feedbacks table, which should update the average rating values in the events table. Here’s how the raw code of such a trigger looks like:

create or replace function UpdateAverageRating() returns trigger AS
$$
BEGIN
UPDATE events SET average_rating=(
SELECT avg(rating) FROM feedbacks
WHERE event_id=NEW.event_id
GROUP BY event_id
)

WHERE id = NEW.event_id
END
$$
language plpgsql

Fortunately, the translation of such a trigger into SQLAlchemy-speak is not only easy, but also very elegant. The imports I showed above already set the context for this translation.

The event model class looks like the following:

class Event(db.Model):
    """Event object table"""
    __tablename__ = 'events'
    __versioned__ = {
'exclude': ['schedule_published_on', 'created_at']
}
    id = db.Column(db.Integer, primary_key=True)
    identifier = db.Column(db.String)
    name = db.Column(db.String, nullable=False)
    external_event_url = db.Column(db.String)

    

    

    

The list of attributes continues, and to the end of this list, we now add a decorated method:




xcal_url = db.Column(db.String)
is_sponsors_enabled = db.Column(db.Boolean, default=False)
discount_code_id = db.Column(db.Integer, db.ForeignKey(
'discount_codes.id', ondelete='CASCADE'))

@aggregated('feedbacks', db.Column(db.Float))
def average_rating(self):
    return db.func.avg(Feedback.rating)

That’s it with the translation – this slick, decorated method can be thought of as a bridge between Python and the trigger shown earlier that’s usually implemented in the database itself. Once this method is added, we save the model file and perform a database migration:

$ python manage.py db migrate

This generates a migration file associated with our changes. This file shows the following alembic migration code:

"""empty message

Revision ID: 1471fe0d04ee
Revises: 49f3a33f5437
Create Date: 2018-06-08 19:32:47.485543

"""

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '1471fe0d04ee'
down_revision = '49f3a33f5437'


def upgrade():
    op.add_column('events', sa.Column('average_rating', sa.Float(), nullable=True))
    op.add_column('events_version', sa.Column('average_rating', sa.Float(), autoincrement=False, nullable=True))

def downgrade():
    op.drop_column('events_version', 'average_rating')
    op.drop_column('events', 'average_rating')

Now that the file is generated, we upgrade our database state by utilizing this migration file:

$ python manage.py db upgrade

And here are the successful migration logs that immediately follow the upgrade command:

INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade 49f3a33f5437 -> 1471fe0d04ee, empty message

 

This completes the implementation of the average rating attribute of events. We can use the same technique to implement other attributes in our server, like sum, and minimum, just to name a couple. We saw how SQLAlchemy so elegantly manages to map all the mapping from Python code to database commands. This is just one of the plethora of advantages of using database object-relational-mappers (ORMs), and Open Event Server utilizes them to full extent.

Resources:

Continue ReadingImplementing Event Average Rating with SQLAlchemy

Enforcing Constraints Throughout a Flask Back-End

Recently it was discovered that Open Event Server does not validate attendees’ tickets. Specifically, it was possible to create an arbitrary number of attendees who’d be attending an event on the same ticket! To fix this, a constraint had to be set up across different layers of Open Event Server, which is based on Flask and Postgres. This post will demonstrate how the constraint was added in the server, and these steps should apply in general to any Flask-based server with a relational back-end.

First of all, the immediate idea that comes after investigating such an issue, is to add a UNIQUE constraint to the database. For this specific case, the problem was in ticket_holders table of the Open Event database. There was originally no check imposed on the ticket_id and event_id columns.

As can be seen in the ticket_holders schema (using the \d+ ticket_holders command), there is no mention of uniqueness on either column. The initial guess was that the combination of ticket_id and event_id should be unique throughout the table to avoid multiple holders attending on the same ticket. However,imposing uniqueness on just the ticket_id column would’ve also worked. So, to be on the safer side, I moved ahead by adding uniqueness on both the columns.

To fix this, we need to make changes to the ticket_holder model. So, in the ticket_holder model file, we add a __table_args__ attribute to the TicketHolder class. This attribute represents the various constraints imposed on the ticket_holders table:

class TicketHolder(db.Model):
    __tablename__ = "ticket_holders"
    __table_args__ = (
db.UniqueConstraint('ticket_id', 'event_id', name='ticket_event'),
) # this is the constraint we add

    id = db.Column(db.Integer, primary_key=True)
    firstname = db.Column(db.String, nullable=False)
    lastname = db.Column(db.String, nullable=False)






The TicketHolder class has attributes named ticket_id and event_id, so to add a unique constraint over them, we pass their names to the UniqueConstraint constructor. Also, any suitable name can be given to the constraint, I chose ‘ticket_event’ to simply emphasize the relationship. Now that we’ve edited the database model file, we have to perform a database migration.

Before we command the migration, we have to remove the entries that potentially violate the constraint we just imposed. As a temporary fix, I connected to the database and deleted all non-unique rows via plain SQL. For a more consistent fix, I will implement this simple deletion code in the database migration file, if need be. So, once the non-unique rows are gone, we perform the database migration as follows:

$ python manage.py db migrate

And then,

$ python manage.py db upgrade

These commands may be different for different projects, but their purpose is the same – to update the database. The upgrade command generates a migration file which looks as follows:

from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils


# revision identifiers, used by Alembic.
revision = '9d21de792967'
down_revision = '194a5a2a44ef'


def upgrade():
op.create_unique_constraint('ticket_event', 'ticket_holders', ['ticket_id', 'event_id'])

def downgrade():
op.drop_constraint('ticket_event', 'ticket_holders', type_='unique')

We can see that the upgrade() function has the command for adding our constraint. Once the database has been upgraded, we can revisit the schema of ticket_holders table (using the \d+ ticket_holders command again). Now we can see that our constraint is added very well in the table schema.

Now, if one tries to create multiple attendees that attend on the same ticket, s/he gets a 500 server error. Here are the related server logs:

2018-06-05 22:04:03.824 IST [46705] ERROR:  duplicate key value violates unique constraint "ticket_event"
2018-06-05 22:04:03.824 IST [46705] DETAIL:  Key (ticket_id, event_id)=(2, 6) already exists.
2018-06-05 22:04:03.824 IST [46705] STATEMENT:  UPDATE ticket_holders SET event_id=6 WHERE ticket_holders.id = 16
127.0.0.1 - - [05/Jun/2018 22:04:03] "POST /v1/attendees HTTP/1.1" 500 -
INFO:werkzeug:127.0.0.1 - - [05/Jun/2018 22:04:03] "POST /v1/attendees HTTP/1.1" 500 -

To get a more graceful error, we also need to make changes in the API schema. This will also allow to validate the data before it gets to the database. So, in the attendees.py file, we need to add a check. This check should extract the ticket and event ids from the data posted and see whether there is already an attendee in the database attending that event on the same ticket. If such an attendee is discovered, the check should raise an error and report it back to the API caller. The suitable place for this check is the before_post() method of the AttendeeListPost class. In any Flask app serving a REST API, such a method (perhaps of a different name) should exist in the API file corresponding to a model. Our check looks like the following within the before_post() method:

from flask_rest_jsonapi import ResourceList
from app.api.helpers.exceptions import ConflictException
from app.models import db
from app.models.ticket_holder import TicketHolder






class AttendeeListPost(ResourceList):
"""
List and create Attendees through direct URL
"""

def before_post(self, args, kwargs, data):
"""
Before post method to check for required relationship and proper permissions
:param args:
:param kwargs:
:param data:
:return:
"""
require_relationship(['ticket', 'event'], data)







if db.session.query(TicketHolder.id).filter_by(
ticket_id=int(data['ticket']), event_id=int(data['event'])
).scalar() is not None:
raise ConflictException(
{'pointer': '/data/attributes/ticket_id'},
"Attendee with this ticket already exists for the same event"
)

Once this check is implemented, we’re all good to go. Now, if an attendee is created that maps to a ticket belonging to an already existing attendee, the following error is sent back to the API caller:

{
"errors": [
{
"status": 409,
"source": {
"pointer": "/data/attributes/ticket_id"
},
"title": "Conflict",
"detail": "Attendee with this ticket already exists for the same event"
}
],
"jsonapi": {
"version": "1.0"
}
}

This completes our work of enforcing this constraint throughout our Flask server. This leads to a more consistent database and potentially avoids confusion at actual events!

Resources:

Continue ReadingEnforcing Constraints Throughout a Flask Back-End

Adding System Messages on Open Event Server

The Open Event Server enables organizers to manage events from concerts to conferences and meetups. It offers features for events with several tracks and venues. Event managers can create invitation forms for speakers and build schedules in a drag and drop interface. The event information is stored in a database. The system provides API endpoints to fetch the data, and to modify and update it.

The Open Event Server is based on JSON 1.0 Specification and hence build on top of Flask Rest Json API (for building Rest APIs) and Marshmallow (for Schema).

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

Model Updation

For the System Messages, we’ll make update model as follows

Now, let’s try to understand this Schema.

In this feature, we are providing Admin the rights to read email and notification formats used in Open Event application.

  1. First of all, there is the need to know that it has three columns notification_status, user_control_status and mail_status of type boolean.
  2. Next it has action attribute which is of type String.
  3. At last, we have hybrid properties email_message and notification_message which will return the format of email and notification respective to the action string.
  4. The hybrid properties depends on _email_message method and _notification_message method. These methods reads the MAILS and NOTIFS dictionaries and return there values corresponding to string of action key of corresponding record.

Schema Creation

For the System Messages, 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 read email and notification formats used in Open Event application.

  1. First of all, there is the need to know that it has three boolean properties notification_status, user_control_status and mail_status
  2. Next it has action attribute which is of type String and it’s value can be validated to have any one of the list provided in choices.
  3. At last, it has the String attributes email_message and notification_message which will return the action formats of email and notification concerning the action string provided.

So, we saw how System Messages Schema and Model is created / updated to allow Admin users to read it’s values.

Resources

Continue ReadingAdding System Messages on Open Event Server

Add Edit user Modal in Open Event Frontend

This blog article will illustrate how the UI for the edit user modal is implemented  and Users API has been integrated into it in Open Event Frontend.

The admin can make any user the admin, sales admin or the marketer of the app. In the route admin/users there is an ember table where all the users are listed. In the table there exists a column named ‘Action Buttons’.

When the edit action button is clicked a modal appears on the screen. Them template of the modal is as follows:

class="content">
class="ui form">

class="ui header">{{t 'Provide admin access?'}}

class="grouped inline fields">
class="field"> {{ui-radio name="isAdmin" label="Yes" value=true onChange=(action (mut isAdmin))}} {{ui-radio name="isAdmin" label="No" value=false onChange=(action (mut isAdmin))}}
</div> <h4 class="ui header">{{t 'Custom system roles'}}</h4>
class="field"> {{ui-checkbox label="Sales Admin" onChange=(action (mut checked))}} {{ui-checkbox label="Marketer" onChange=(action (mut checked))}}
<button class="ui teal right floated submit button update-changes"> {{t 'Save'}} </button> </div> </div>

For the API integration the users model is used. The attributes isAdmin, isSalesAdmin, isMarketer from the model are used to send a patch request to the server. The modal has basically to parts. The first part consists of radio buttons through which the super admin has the rights to create a user an admin of the app or to remove his role as the admin. The second part consists of checkboxes through which the user can get the custom system role to be the sales admin or the marketer. A get request is sent to the user’s model in the server and the initial values of the modal are decided.

If the admin changes some value, he clicks on the save button in the modal and a patch request is sent to the server. The save function is written in the modal’s component.

actions: {
saveRole(id) {
this.get('store').findRecord('user', id).then(function(user) {
user.save();
});
this.set('isOpen', false);
},
toggleSalesAdmin(user) {
user.toggleProperty('isSalesAdmin');
},
toggleMarketer(user) {
user.toggleProperty('isMarketer');
},
createAdmin(user, isAdmin) {
user.set('isAdmin', isAdmin);
}
}

Resources

Continue ReadingAdd Edit user Modal in Open Event Frontend

Adding the User Settings Route in Admin User Route Open Event Frontend

This blog article will illustrate how the User settings API has been integrated into the admin users route  Open Event Frontend. The admin can change the contact info of some user, details about the email preferences for different events created by the user and the third party authentication details entered by the user.

To make the settings user link in the user link column of the users table functional new sub routes are added to the app’s user route as follows:

  • /admin/users/<user_id>/settings/contact-info
  • /admin/users/<user_id>/settings/email-preferences
  • /admin/users/<user_id>/settings/applications

The template for the index route which redirects to each of the settings route is:

class="ui grid">
class="row">
class="twelve wide column"> {{#tabbed-navigation}} {{#link-to 'admin.users.view.settings.contact-info' model.user.id class='item'}} {{t 'Contact Info'}} {{/link-to}} {{#link-to 'admin.users.view.settings.email-preferences' model.user.id class='item'}} {{t 'Email-Preferences'}} {{/link-to}} {{#link-to 'admin.users.view.settings.applications' model.user.id class='item'}} {{t 'Applications'}} {{/link-to}} {{/tabbed-navigation}}
</div> {{outlet}} </div>

Interestingly, the routes admin/users/view and admin/users/list are both dynamic and expect a parameter after /users/ hence, the app cannot distinguish between them on it’s own, thus explicit handling of the dynamic parameter of the routes was implemented, differentiating them on the basis of the route’s state as follows:

beforeModel(transition) {
this._super(...arguments);
const userState = transition.params[transition.targetName].users_status;
if (!['all', 'deleted', 'active'].includes(userState)) {
this.replaceWith('admin.users.view', userState);
}
}

Thus if the dynamic portion of the route doesn’t contain the parameters all, deleted or active, then it must be referring to a user’s events or sessions and the route needs to be replaced with the desired events or sessions route accordingly.

The server is queried to fetch the details of a given user like the email,  contact, various events created by the user to get the email and notification preferences. For getting each detail the current users model is returned and the values in the model are returned to the form.

For the contact-info sub route the values like the email and the contact number are fetched and are shown in the form. There is a save button in the form too. The admin can change this information and send a patch request to the server by clicking this button.

 updateContactInfo() {
this.set('isLoading', true);
let currentUser = this.get('model.user');
currentUser.save()
.then(() => {
this.get('notify').success(this.get('l10n').t('Your Contact Info has been updated'));
})
.catch(() => {
this.get('notify').error(this.get('l10n').t('An unexpected error occurred'));
})
.finally(() => {
this.set('isLoading', false);
});
}

For the email-preferences sub route the model has attributes like sessionSchedule, nextEvent etc.and the admin has the access to change the email-notifications for any event  created by any user. The client side has checkboxes to show the data to the user. The states of the checkboxes are determined by the data that we receive from the API. We also let the admin change the preferences of the email-notifications so that he can customise the notifications and keep the ones he wants some user to receive.

{{settings/email-preferences-section preferences=model}}

The sub route for email preferences:

export default Route.extend(AuthenticatedRouteMixin, {
titleToken() {
return this.get('l10n').t('Email Preferences');
},
model() {
const currentUser = this.modelFor('admin.users.view');
return currentUser.query('emailNotifications', { include: 'event' });
}
});

So, the admin has the access to change the information and the email notifications of a user.

Resources

Continue ReadingAdding the User Settings Route in Admin User Route Open Event Frontend