Weather API - bounswe/2021SpringGroup9 GitHub Wiki

1. Glossary

  • Story: A model in the database. It is a model for user's stories that they post in our app. Its fields are:
    • id: Story's id to be kept in the database.
    • title: Story's title with maximum char length 200
    • story: Story's content with maximum char length 1000
    • name: The user's name who posted the story. It has a maximum char length of 200.
    • longitude: Story's location's longitude.
    • latitude: Story's location's latitude.
    • location: Story's location name with maximum char length 200.
    • tag: Story's tag with maximum char length 200.
    • date: Story's post date.
    • notifyAdmin: Shows if a story needs to notify the admin or not.
  • OpenWeatherMap API: This API is used to get a locations weather information at the time that request has been sent.

2. Description

My API, when a get request has been sent, gets the story with the given ID. Then taking its longitude and latitude, it send a request to third party API (OpenWeatherMap API)with this headers and an API key taken from .env file. Then it takes this information and filters out the required information. Makes Kelvin to Celcius transformations. Timezone transformation (third party API provides this information with minutes, I changed it to hours to make it more understandable). Then it adds a comment about the weather.

3. Functionality

  • Get current weather information of the location of story.

Request:

GET http://localhost:8000/api/weather/<story_id>

Parameters:

{

story_id = The id of the Story

}

Response:

{
    "condition": "X",
    "temperature": X,
    "feel": X,
    "wind": X,
    "country": "X",
    "time_zone": X,
    "comment": "X"
}

4. Example Responses

  • GET /api/weather/<valid story id>
{
    "condition": "Clouds",
    "temperature": 27.08,
    "feel": 27.31,
    "wind": 4.87,
    "country": "TR",
    "time_zone": 3,
    "comment": "Temperatures are great! Go out there and have fun!"
}
  • GET /api/weather/<invalid story id (story with that id does not exist)>
Story does not exist
  • GET /api/weather/<invalid story id (id is not an integer)>
Only integer values are used by this API
  • POST/DELETE/PUT /api/weather/<valid story id>
{
    "detail": "Method \"POST/DELETE/PUT\" not allowed."
}
  • GET /api/weather/<valid story id> But this time OpenWeatherMap API is not operational (either servers are down or API key is invalid).
Could not send request to OpenWeather API

5. Authentication

To use Weather API, there is no need for authentication. But since it uses OpenWeatherMap API, and OpenWeatherMap uses API keys, there should be an API key for using it. It should be stored in .env file.

6. Errors and Successes

More information about the status codes can be found in: https://httpstatuses.com/

Name Meaning
HTTP_200_OK Successful Request
HTTP_400_BAD_REQUEST If given story id is not integer or connection can not be established with OpenWeatherMap
HTTP_404_NOT_FOUND Error when story with that id does not exists.
HTTP_405_METHOD_NOT_ALLOWED Error when other requests than GET is requested.

7. Code Documenation

  • Models Used in Weather API

This API uses only the Story model.

class Story(models.Model):
    title = models.CharField(max_length=200)
    story = models.CharField(max_length=1000)
    name = models.CharField(max_length=200)
    longitude = models.FloatField()
    latitude = models.FloatField()
    location = models.CharField(max_length=200)
    tag = models.CharField(max_length=200)
    date = models.DateTimeField(auto_now_add=True)
    notifyAdmin = models.BooleanField(default=False)

    def __str__(self):
        return self.title 
  • Views
@api_view(['GET'])
def weather(request,story_id):

    if(request.method!='GET'):
        httpresponse=HttpResponse('Only GET method is available for this API')
        httpresponse.status_code=405
        return httpresponse

    if(not isinstance(story_id,int)):
        httpresponse=HttpResponse('Only integer values are used by this API')
        httpresponse.status_code=400
        return httpresponse

    try:
        story=Story.objects.get(id=story_id)
    except Story.DoesNotExist:
        httpresponse=HttpResponse('Story does not exist')
        httpresponse.status_code=404
        return httpresponse
        
    resp=requests.get("http://api.openweathermap.org/data/2.5/weather?lat=%s&lon=%s&appid=%s" % (story.latitude,story.longitude,WEATHER_API_KEY))

    if(resp.status_code!=200):
        httpresponse=HttpResponse('Could not send request to OpenWeather API')
        httpresponse.status_code=400
        return httpresponse
    
    weather=resp.json()
    condition=weather['weather'][0]['main']
    temperature=round(weather['main']['temp']-(273.15),2)
    feel=round(weather['main']['feels_like']-(273.15),2)
    wind=weather['wind']['speed']
    country=weather['sys']['country']
    timezone=round(weather['timezone']/3600)
    


    if(temperature<0):
        comment="Wow it is freezing out there!"
    elif(temperature<15):
        comment="It is cold!"
    elif(temperature<30):
        comment="Temperatures are great! Go out there and have fun!"
    else:
        comment="It is hot! You sure you are OK?"

    return JsonResponse({
        'condition':condition,
        'temperature':temperature,
        'feel':feel,
        'wind':wind,
        'country':country,
        'time_zone':timezone,
        'comment':comment
    })
  • Tests
class WeatherTest(TestCase):
    def setUp(self):
        """
        An instance that works
        """
        Story.objects.create(
            title = "Title1",
            story = "Story1",
            name = "User1",
            longitude = 41,
            latitude = 28,
            location = "Istanbul",
            tag = "Tag1",
            notifyAdmin = False
        )
        """
        An instance with wrong longitude.
        """
        Story.objects.create(
            title = "Title2",
            story = "Story2",
            name = "User2",
            longitude = 4100,
            latitude = 28,
            location = "Not Istanbul",
            tag = "Tag2",
            notifyAdmin = True
        )

    """
    Send post request. Should not be allowed.
    """
    def test_post_requests(self):
        c=Client()
        resp=c.post("/api/weather/1")
        self.assertEqual(resp.status_code,405)
    
    """
    Send put request. Should not be allowed.
    """
    def test_put_requests(self):
        c=Client()
        resp=c.put("/api/weather/1")
        self.assertEqual(resp.status_code,405)
    
    """
    Send delete request. Should not be allowed.
    """
    def test_delete_requests(self):
        c=Client()
        resp=c.delete("/api/weather/1")
        self.assertEqual(resp.status_code,405)

    """
    Test when an instance does not exists.
    """
    def test_when_story_does_not_exist(self):
        c=Client()
        resp=c.get("/api/weather/3")
        self.assertEqual(resp.status_code,404)
    
    """
    OpenWeather API fails with wrong longitude or latitude.
    """
    def test_when_openweather_api_fails(self):
        c=Client()
        resp=c.get("/api/weather/2")
        self.assertEqual(resp.status_code,400)
    
    """
    This is an instance that works.
    """
    def test_works_correct(self):  
        c=Client()  
        resp=c.get("/api/weather/1")
        self.assertEqual(resp.status_code,200)

    """
    When parameter is not an integer type.
    """
    def test_string_type_parameter(self):  
        c=Client()  
        resp=c.get("/api/weather/mert")
        self.assertEqual(resp.status_code,400)
⚠️ **GitHub.com Fallback** ⚠️