Quotes API - bounswe/2021SpringGroup9 GitHub Wiki

Quotes API Documentation


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: Thi shows if a story needs to notify the admin or not.
  • Quote: A model in the database. It is a model for liked quotes in the database which were requested by using a story in the database. Its fields are:
    • id: Quote's id in the FavQS API.
    • author: Quote's owner's name with maximum char length 255.
    • body: Quote's content with maximum char length 2047.
    • likes: How many times the quote is liked.
  • quote_id: Quote id as in FavQs API.
  • story_id: Story id as in database.
  • FavQs API: An API to get quotes on different topics by different people. One can use filters to get quotes with a specific tag or word.

2. API Reference

Quote API is a RESTful API used to get quotes that are tagged with a story's tag or contain its location name. If the quote is posted (liked), it is added to the database by increasing the number of likes.

It uses FavQs API as an external API and sends requests according to the feature wanted by the Quotes API. The features to get a quote with tag and filter from FavQs are used.

Quote API has a list URL that it accepts which can be easily approached with the help of their functional name conventions and returns JSON-encoded responses as well as HTTP response codes.

After running the dockerized practice-app, the API's base URL is: http://3.129.194.233/ or http://ec2-3-129-194-233.us-east-2.compute.amazonaws.com/

3. Features

Get Quote that is tagged with the story's tag

Request:

GET http://3.129.194.233/api/quote/int:story_id

Parameters:

{

story_id = The id of the Story

}

Response:

"GET /api/quote/1 HTTP/1.1" 200 9893

Data:

{
'id': Quote['id'],
'Quote': Quote['body'],
'Author': Quote['author'],
'Likes': Quote['favorites_count']
}
Get Quote that contains the Location name of the story

Request:

GET http://3.129.194.233/api/quote/location/int:story_id

Parameters:

{

story_id = The id of the Story

}

Response:

"GET /api/quote/location/2 HTTP/1.1" 200 9988

Data:

{
'id': Quote['id'],
'Quote': Quote['body'],
'Author': Quote['author'],
'Likes': Quote['favorites_count']
}
Like a quote and add to Database

Request:

POST http://3.129.194.233/api/postquote/int:quote_id

Parameters:

{

quote_id = The id of the quote same as in FavQs API

}

Response:

  • GET not allowed:
"GET /api/postquote/4 HTTP/1.1" 405 10181
  • POST allowed:
"POST /api/postquote/4 HTTP/1.1" 200 10279

Data:

  • GET not allowed:
{
"detail": "Method \"GET\" not allowed."
}
  • POST allowed:
{
"id":Quote['id'],
"author": Quote['author'],
"body": Quote['body'],
"likes": Quote['favorites_count']+1
}

4. Example Successful Response Data

  • GET /api/quote/<valid story id in database>
{
    "id": 57514,
    "Quote": "If one had but a single glance to give the world, one should gaze on Istanbul.",
    "Author": "Alphonse de Lamartine",
    "Likes": 0
}
  • POST /api/postquote/<valid quote id in FavQs>
{
    "id": 57514,
    "author": "Alphonse de Lamartine",
    "body": "If one had but a single glance to give the world, one should gaze on Istanbul.",
    "likes": 1
}

5. Authentication

The Quotes API doesn't require any keys for authentication. It uses FavQs API as the third party API which contains several quotes about different topics.

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_201_CREATED Successful POST request when Quote is not in the database
HTTP_400_BAD_REQUEST Errors when Quote and Story with id don't exist in the database
HTTP_404_NOT_FOUND Errors when no Quote with that id is in FavQs

7. Code Documentation

Models Used in Quotes API

Models can be used to test the API functionalities and understand the code. There are two models used for this API: Quote and Story. Other models are used in the general practice app and can be found in the general documentation.

class Quote(models.Model):
    id = models.IntegerField(primary_key=True)
    author = models.CharField(max_length=255)
    body = models.CharField(max_length=2047)
    likes = models.IntegerField()

    def __str__(self):
        return self.body

Here, a row in the Quote table is created in the database when a quote is posted using postquote/<int:id>. Here, Quote.id is the id of the quote retrieved from FavQs API. The quote model has an author to whom the quote belongs, a body (the quote itself), and likes (the number of likes associated with that Quote).

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

Here, a row in the Story table is created when a story is posted using our Practice App storypost/. User needs to specify the fields title, story (the content of the story), name (user's name who posts the story), longitude, latitude, location of the story, tag (a word to describe the story), date of the story, notifyAdmin when admin is to be notified to check the content of the story.

Helper Functions
def no_quote_helper(param):

    url ="https://favqs.com/api/qotd"
    headers = {"Authorization": 'Token token="{}"'.format(QUOTE_API_KEY)}

    try:
        response = requests.get(url, headers=headers, params=param)
        quote = response.json()
        q = quote['quote']
        return q
    except: 
        return "No quotes found"

no_quote_helper function returns a random quote from FavQs API. This is used when no quotes with a given tag or location are found. It returns "No quotes found" to send a message to GetQuoteTag and GetQuoteLoc for them to raise an exception.

def most_like_quote_helper(quote):
    likemax = -1
    likeselect = -1

    for i in range(len(quote['quotes'])):
        if quote['quotes'][i]['favorites_count'] > likemax: # get the quote with the most likes tagged with the story's tag in the Favqs API
            likemax = quote['quotes'][i]['favorites_count']
            likeselect = i

    return quote['quotes'][likeselect]

most_like_quote_helper function selects the quote with the most likes in a given quote list. It then returns the quote with the most likes. This is used in GetQuoteTag and GetQuoteLoc.

Views
class GetQuoteTag(APIView):    
    """
    Get quotes that are tagged with the tag of the given story. 
    """    
    def get(self, request, pk):

        try:
            story=Story.objects.get(pk=pk)
        except: 
            return Response(status=status.HTTP_400_BAD_REQUEST)
        tag = story.tag

        param = {'filter': tag.lower(), 'type': "tag"}
        url = "https://favqs.com/api/quotes/"
        headers = {"Authorization": 'Token token="{}"'.format(QUOTE_API_KEY)}

        try:
            response = requests.get(url, headers=headers, params=param)
            quote = response.json()

            q = most_like_quote_helper(quote)

            if q['body'] == 'No quotes found': # If a quote with that tag doesn't exist return random quote
                q = no_quote_helper(param)
                if q == "No quotes found": # If no random quote is returned
                    raise Exception(q)
            
            qselect = {'id': q['id'],'Quote': q['body'], 'Author': q['author'], 'Likes': q['favorites_count']}
            return Response(qselect, status=status.HTTP_200_OK)
        except:
            return Response(response.status)
        

GetQuoteTag view is used to get the quotes that are tagged with the story's tag. It uses FavQs API to retrieve the quotes and uses filters to filter according to the given tag. It starts with finding the story with the given id and takes its tag. If such a story is not found HTTP_400_BAD_REQUEST is returned as a response. The request takes the URL and authentication key of FavQs API as well as param = {'filter': tag, 'type': "tag"} as parameters. Here, the filter and the tag keys are given as asked by the FavQs API. The response from the FavQs is a list of quotes and the code returns the one with the most likes by using the for loop. FavQs returns No quotes found string as a response when no quotes with the given parameters exist. I used the same string to return the same response. In that case, a random quote is returned so that each story has a quote under them.
If the quote is found, HTTP_200_OK and the quote's id, body, author, and likes are returned.

lass GetQuoteLoc(APIView):
    """
    Get quotes that contain the location of the given story. 
    """    
    def get(self, request, pk):
        likemax = -1
        likeselect = -1
        try:
            story=Story.objects.get(id=pk)
        except:
            return Response(status=status.HTTP_400_BAD_REQUEST)
        location = story.location
        param = {'filter': location}
        url = "https://favqs.com/api/quotes/"
        headers = {"Authorization": 'Token token="{}"'.format(QUOTE_API_KEY)}
        try:
            response = requests.get(url, headers=headers, params=param)
            quote = response.json()
            for i in range(len(quote['quotes'])):
                if quote['quotes'][i]['favorites_count'] > likemax: # get the quote with the most likes containing the location of the story
                    likemax = quote['quotes'][i]['favorites_count']
                    likeselect = i
            q = quote['quotes'][likeselect]
            if q['body'] == 'No quotes found': # if no quote contains the location name
                return Response(q['body']+" with location " + location, status=status.HTTP_400_BAD_REQUEST)
            
            qselect = {'id': q['id'],'Quote': q['body'], 'Author': q['author'], 'Likes': q['favorites_count']}
            return Response(qselect, status=status.HTTP_200_OK)
        except:
            return Response(response.status)

GetQuoteLoc is similar to GetQuoteTag except that GetQuoteLoc gets the quotes that contain the location name of the story. The code is the same except param = {'filter': location} is different and is adjusted to get the location as a filter.

class FavQuote(APIView):
    """
    Add the quote to the database if it is liked. Only the quotes that are retrieved from the Favqs API can be posted.
    """
    def post(self, request, pk):
        quote = {}
        try:
            quote = Quote.objects.get(id=pk)
            quote.likes += 1  # if it is already in the database, increase the number of likes after post request
            quote.save()
            serializer = QuoteSerializer(quote)
            return Response(serializer.data, status=status.HTTP_200_OK)
        except Quote.DoesNotExist:
            url = "https://favqs.com/api/quotes/"+str(pk)
            headers={"Authorization": 'Token token="{}"'.format(QUOTE_API_KEY)}
            response = requests.get(url, headers=headers).json()

            if 'status' in response and response["status"] == 404:
                return Response(response, status=status.HTTP_404_NOT_FOUND)

            quote = {"id":response['id'], "author": response['author'], "body": response['body'], "likes": response['favorites_count']+1}
        
            serializer = QuoteSerializer(data=quote)
            if serializer.is_valid():
                serializer.save()
                return Response(serializer.data, status=status.HTTP_201_CREATED)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

FavQuote has the POST functionality. It takes a quote and saves it to the database with like number increased. This way, if a quote is liked by the user, that quote is added to the database for later use. If the liked quote is already in the database, then the code only increases its number of likes by 1 and saves it. If the quote doesn't exist in the database, then the code sends a request to FavQs API to get the quote with the given id as a parameter. If no quotes with that id are found in FavQs, then the status returns HTTP_404_NOT_FOUND. Else, the quote is saved to the database with an increased number of likes.

Tests
self.client = Client()
        self.story1 = Story.objects.create(
                id = 1, 
                title = "A Wonderful Day in Istanbul", 
                story = "It was my first time visiting Istanbul. It is full of places to see and enjoy. Can't wait to visit Istanbul again!",
                name = "Leyla", 
                longitude = 41,
                latitude = 28,
                location = "Istanbul",
                tag = "Joy"
        )
        
        self.story2 = Story.objects.create(
                id = 2,
                title = "A Wonderful Day in Istanbul", 
                story = "It was my first time visiting Istanbul. It is full of places to see and enjoy. Can't wait to visit Istanbul again!",
                name = "Leyla", 
                longitude = 41,
                latitude = 28,
                location = "",
                tag = "Joy"
        )

        self.quote = Quote.objects.create(
                id = 700,
                author = "John Keats",
                body = "A thing of beauty is a joy forever: its loveliness increases it will never pass into nothingness.",
                likes = 0
        )
def test_get_quote_tag(self):
        response = self.client.get(reverse('get_quote_tag', args=(self.story.id,)))
        self.assertNotEqual(response.status_code, 400)
def test_get_quote_tag_success(self):
        response = self.client.get(reverse('get_quote_tag', args=(self.story.id,)))
        self.assertEqual(response.status_code, 200)
    def test_get_quote_loc(self):
        response = self.client.get(reverse('get_quote_loc', args=(self.story.id,)))
        self.assertNotEqual(response.status_code, 400)
    
    def test_get_quote_loc_success(self):
        response = self.client.get(reverse('get_quote_loc', args=(self.story.id,)))
        self.assertEqual(response.status_code, 200)
    def test_quote_loc_exist(self):
        response = self.client.get(reverse('get_quote_loc', args=(self.story.id,)))
        self.assertIsNotNone(response.data['Quote'])
    def test_quote_response(self):
        response = self.client.post(reverse('fav_quote', args=(self.quote.id,)))
        self.assertNotEqual(response.status_code, 400)
    def test_post_success(self): 
        response = self.client.post(reverse('fav_quote', args=(self.quote.id,)))
        self.assertEqual(response.status_code, 200)
def test_post_like(self):
        likes = self.quote.likes
        response = self.client.post(reverse('fav_quote', args=(self.quote.id,)))
        self.assertEquals(likes+1, response.data['likes'])
⚠️ **GitHub.com Fallback** ⚠️