Skip to content

Individual Contribution Report 2 | Ahmet Kudu

ahmetkudu edited this page May 12, 2023 · 2 revisions

Links to essential issues (git issues)

  • Researching about world time API #151
  • Creating a Web UI #156
  • Requesting API supporting the GET and POST methods to show world time #157
  • Creating database and login page in practice-app #168
  • Keep the last world time API request #171
  • Creating unit tests on WorldTime API #173
  • Change database structure #179
  • Documentation of how to use World Time API #183

The name, route, and description of utilized third-party URIs

The API name used is Time Zone API, which provides a full range of capabilities for programmers and companies needing to manage time across several locations. Users of this API may quickly get time zone data, convert time across time zones, and keep track of daylight saving time with the help of this API.

There are routes provided by Time Zone API. I used two of them, as shown below. The former accepts a time zone parameter (Example: Europe/Amsterdam) and returns local time information in JSON format. The latter is about the post method accepting a JSON, including dateTime and languageCode values, and responses as JSON, including time information with a natural language according to languageCode given. Screenshot 2023-05-12 at 13 48 40

The name, route, and description of the created API functions

After logging into the web app, remember that if you don't have an account, please sign up; then the below page will be seen if using the endpoint named /worldtime. Here, there are two text boxes needed to be filled. The first is about time zone, and the second is about language code. If you don't know how to type a time zone, look at the link. After typing a timezone to the relevant textbox, you have to fill the language code area by writing one of the language codes, which is tr for Turkish, en for English, de for German, fr for French, it for Italian, es for Spain. Screenshot 2023-05-12 at 00 30 27

Let's do one exercise by typing Europe/Monaco as time zone and tr as Turkish language code. After clicking the search button, you can see the local time in Monaco with the preferred language. Screenshot 2023-05-12 at 00 43 56

The response to this request will be recorded in the database with your account, which is technically the user id. This means that if you use this API next time, you can see the same time zone, which is Europe/Monaco, and the language code, which is tr again shown below.
Screenshot 2023-05-12 at 00 53 02

Here are the main codes I wrote for world time API.

@app.route(
    "/worldtime", methods=["GET", "POST"]
)  # it is a decorator we have to put a function under of it
@login_required
def worldTime():
    world_time = (
        session.query(WorldTimeTable)
        .filter(WorldTimeTable.user_id == current_user.id)
        .order_by(WorldTimeTable.id.desc())
        .first()
    )
    if request.method == "POST":
        timezone = request.form.get("query")
        if timezone == "":
            return render_template("worldtime.html", error="Timezone cannot be empty!")
        language_code = request.form.get("languageCode")
        if language_code == "":
            return render_template(
                "worldtime.html", error="Language code cannot be empty!"
            )
        response = requests.get(
            "https://timeapi.io/api/Time/current/zone?timeZone=" + timezone
        )
        response = response.json()
        if response == "Invalid Timezone":
            return render_template("worldtime.html", error="Timezone is not found!")
        else:
            if len(str(response["month"])) == 1:
                month = "0" + str(response["month"])
            else:
                month = str(response["month"])

            if len(str(response["day"])) == 1:
                day = "0" + str(response["day"])
            else:
                day = str(response["day"])

            if len(str(response["hour"])) == 1:
                hour = "0" + str(response["hour"])
            else:
                hour = str(response["hour"])

            if len(str(response["minute"])) == 1:
                minute = "0" + str(response["minute"])
            else:
                minute = str(response["minute"])

            if len(str(response["seconds"])) == 1:
                seconds = "0" + str(response["seconds"])
            else:
                seconds = str(response["seconds"])

            creating_json = {
                "dateTime": str(response["year"])
                + "-"
                + month
                + "-"
                + day
                + " "
                + hour
                + ":"
                + minute
                + ":"
                + seconds,
                "languageCode": request.form.get("languageCode"),
            }
            response = requests.post(
                "https://timeapi.io/api/Conversion/Translate", json=creating_json
            )
            response = response.json()
            if response == "Couldn't find a language with that code":
                return render_template(
                    "worldtime.html", error="Language code is not found!"
                )
            else:
                new_world_time = WorldTimeTable(
                    timezone=timezone,
                    languageCode=language_code,
                    user_id=current_user.id,
                )
                session.add(new_world_time)
                session.commit()
                return render_template("worldtime.html", response=response)
    else:
        if world_time:
            return render_template(
                "worldtime.html",
                timezone=world_time.timezone,
                languageCode=world_time.languageCode,
            )
        return render_template("worldtime.html")

The description of unit tests

In test_worldtime.py, the functions relating to login operation was prepared by Erkam. I used those codes because I need to use a user id when I want to CRUD operations on WorldTimeTable in the database for unique users. The below image represents creating a record relating world time response for a test user, named practiceapp, to the database.

To delete this record in WorldTimeTable in the database, I applied this unit test.

def test_worldtime_post(client):
    client.post("/login", data=dict(
        username=TEST_USER, password=TEST_USERPASS
    ), follow_redirects=True)
    user = User.query.filter_by(username=TEST_USER).first()
    response = client.post("/worldtime", data=dict(
        query="Europe/London", languageCode="en", user_id=user.id
    ), follow_redirects=True)
    assert response.status_code == 200

The below image has to do with testing edge cases relating to provided wrong parameters.

def test_worldtime_post_invalid(client):
    client.post("/login", data=dict(
        username=TEST_USER, password=TEST_USERPASS
    ), follow_redirects=True)
    response = client.post("/worldtime", data=dict(
        query="Europe/London", languageCode=""
    ), follow_redirects=True)
    assert response.status_code == 200


def test_worldtime_post_invalid_timezone(client):
    client.post("/login", data=dict(
        username=TEST_USER, password=TEST_USERPASS
    ), follow_redirects=True)
    response = client.post("/worldtime", data=dict(
        query="", languageCode="en"
    ), follow_redirects=True)
    assert response.status_code == 200


def test_worldtime_post_invalid_timezone_and_language(client):
    client.post("/login", data=dict(
        username=TEST_USER, password=TEST_USERPASS
    ), follow_redirects=True)
    response = client.post("/worldtime", data=dict(
        timezone="", languageCode=""
    ), follow_redirects=True)
    assert response.status_code == 200

Lastly, while Erkam is preparing unit tests about login, he skipped the critical test, which is the logout test. If we don't add this test, the login session is ever alive on the web app. That's why I added this test below to other login tests prepared by Erkam.

def test_logout(client):
    client.post("/login", data=dict(
        username=TEST_USER, password=TEST_USERPASS
    ), follow_redirects=True)
    client.get("/logout", follow_redirects=True)
    response = client.get("/")
    assert response.status_code == 302

Sample calls

You can see two examples of detailed responses of the get method and post method using the DateTime parameter returning the above get method: Screenshot 2023-05-12 at 17 44 43 Screenshot 2023-05-12 at 17 46 55

Significant work related to DB and Login Page

I initialized the database by using the SqlAlchemy.ORM. This helps us eliminate SQL queries directly on the code; instead, you could write all SQL queries with the idea of object class relationship provided SqlAlchemy.ORM. You can see my studies about the database in the issues, namely #168 and #179. So, we could record some information responding from get methods we used by using login information. Such features are provided in the codes I wrote, shown below.

class User(Base, UserMixin):
    __tablename__ = "User"
    id: Mapped[int] = mapped_column(primary_key=True)
    username = Column(String(15), unique=True)
    password = Column(String(120))
    world_time: Mapped[List["WorldTimeTable"]] = relationship()

class WorldTimeTable(Base, UserMixin):
    __tablename__ = "WorldTimeTable"
    id: Mapped[int] = mapped_column(primary_key=True)
    timezone = Column(String(30))
    languageCode = Column(String(15))
    user_id: Mapped[int] = mapped_column(ForeignKey("User.id"))

The login page I prepared provides a nice feature to record some information based on user information according to login. For the excellent usage of login, I used the wtform and flask_login module, which controls the validation of input of username and password. To hide the password entered by a user in the case that this user signed up, I used hashed password mechanism provided werkzeug.security module.

Describe any challenges you met during the implementation

It was very difficult to perform the unit tests in the Implementation section. Because all the endpoints in the structure we have prepared must be the login process before working. I couldn't do this part. Thanks to Erkam solved this problem.

Clone this wiki locally