Message Board Project

For this project you will write a Kivy client that uses a web API to interact with an extremely simple message board server. This project is to be completed individually.

Everybody will be using the same server to test their clients, so everybody will able to see everything you post. I imagine things will get silly, but please be respectful with any content that you post!.

The server is hosted on campus and off-campus access is not allowed, so you will need to use the VPN when interacting with the server.

The API

I have written a web service which you will interact with. There are 2 supported operations: get and post.

get

To get messages from the server, make a request to the following URL:

http://nsommer.wooster.edu/board/get

The response will contain a list of dictionaries, written in JSON. Each dictionary in the list represents one message. The messages are sorted with the most recent message first, and the list is limited to the 50 most recent messages.

Each message dictionary looks like this:

{
   "subject": <message subject>,
   "time": <message timestamp>,
   "body": <message body>
}

<message subject>, <message timestamp>, and <message body> are all strings which will be different depending on the message. The timestamp is in UTC time (5 hours ahead of us before daylight savings, 4 hours ahead during daylight savings) in the following format:

YYYY-MM-DD HH:MM:SS

So here is an example response containing 2 messages:

[
   {
      "subject": "What's up?",
      "time": "2021-02-25 22:57:00",
      "body": "How are you doing?"
   },
   {
      "subject": "Hello!",
      "time": "2021-02-25 21:10:19",
      "body": "Just saying hi!"
   }
]

As we did with the GitHub API, use json.loads() to turn the reponse content into a Python object. In this case the object will be a list of dictionaries.

post

To post a message to the server, use the following URL:

http://nsommer.wooster.edu/board/post

If you paste that URL into a browser you will note that you get the following response:

{
  "message": "The subject must be provided"
}

That is because you must send the subject and the body of the message along with your request. To do this we will use the POST feature of HTTP, which is what your browser uses when you submit forms on websites.

To do this, we use the requests.post() function and in addition to providing the URL, we also provide a dictionary containing the subject and body of the message. The key for the subject must be 'subject' and the key for the body must be 'body'. If the above URL is stored in the variable url, the subject is stored in the variable subject, and the body is stored in the variable body, your post would look something like this:

requests.post(url, data={'subject': subject, 'body': body})

As with requests.get(), requests.post() returns a response from the server. If everything goes OK, you can assume the post went through correctly and you do not need to do anything with the content of the response, but if there is an error you should report it. You can check the status code of the response (discussed below) to see if anything went wrong.

Status Codes

Every HTTP response comes with a status code. As discussed before, status code 200 means everything went OK, and status code 404 means the requested page is not found. There are many other codes as well.

The message board server returns a status code of 400 if the API was used incorrectly, and a status code of 429 if you have exceeded the rate limit (4 requests per second).

Kivy App

Your Kivy app must show the current messages in a ScrollView so that you can scroll through the messages if they take up too much room. Some messages will be long enough that you will want the text to wrap, which will not happen by default. See this blog post for information about how to make that happen:

https://blog.kivy.org/2014/07/wrapping-text-in-kivys-label/

Do not simply display the JSON representation of each message. Instead, show the message in a human-readable way.

You must have a refresh button that will fetch the latest messages when pressed.

You must also have 2 TextInput widgets that can be used to compose a message. One widget is for the subject and the other is for the body of the message. You must also have a submit button to submit the message to the server.

When the submit button is pressed, your app must submit the post to the server and then automatically refresh the messages so that your post is displayed.

Organization

It is good practice to split programs into modules. Here it makes sense to place the functions for getting and posting messages in a separate module. Let’s say you create the functions get_posts() and post_message() and put them in the file board_api.py. Then in the Python file where you create your Kivy app you can import those 2 functions like so:

from board_api import get_posts, post_message

You want to fully separate the API functionality from the Kivy functionality, so your API module must not import any Kivy modules, and the module containing your Kivy code must not import requests or json.

Handling Errors

If anything goes wrong, display an error message by using a PopUp widget. You probably want to set the title, content, and size or size_hint optional parameters.

The title of a PopUp is simply a string that appears at the top of the widget. The content is a widget that is displayed within the PopUp widget. I used a vertical BoxLayout for the content of my PopUp. The BoxLayout contains a Label containing the error message and a Button to press to dismiss the popup. Below is a walkthrough of how to accomplish that with code.

Create the BoxLayout:

content = BoxLayout(orientation='vertical')

Create a Label with whatever the error message is:

message_label = Label(text=error_message)

Create the Button:

dismiss_button = Button(text='OK')

Add the label and the button to the layout:

content.add_widget(message_label)
content.add_widget(dismiss_button)

Create the PopUp. The size_hint specifies the width and height of the widget in terms of the width and height of the root widget, so this popup will be about a third the width of the root window and a quarter of the height:

popup = Popup(title='Error', content=content, size_hint=(0.3, 0.25))

Make it so that pressing the button dismisses the popup:

dismiss_button.bind(on_press=popup.dismiss)

Display the popup:

popup.open()

All of that can be placed into a function you can call whenever you need to pop up an error message.

Your error popup function should not be called from the API module, it should only be called from within the module containing Kivy code. This means that if an error is encountered in your API functions there needs to be a way to report the error to the caller so that it can be handled appropriately. Create a custom exception class in your API module, and if anything goes wrong raise an instance of this exception. Then when you call the API functions, use a try/except block so you can cleanly report errors with a popup.

See the in_class-exceptions example code and the Python tutorial on exceptions for more on exceptions.

Example

Here are some screenshots of my client to give you a better idea of what I am asking for. Your app must have all of the functionality that mine does, but it does not have to look exactly the same.

Submission

In addition to the requirements above, follow PEP 8 and other good style best practices (well named variables, functions, classes, etc.).