Skip to content

Story API

Ahmet Melih Aydoğdu edited this page Jun 14, 2021 · 9 revisions

Story 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.
  • This is the API for using our own application. By using the GET, PUT and DELETE functionalities, users can get the stories from the database, update stories or delete them from the database.

2. How to Use Story API

Get All Stories

This returns Story objects that are stored in the database.

Request:

Parameters:

  • No parameters are needed for this.

Response:

  • "GET /api/story/ HTTP/1.1" 200

Data:

[
    {
        "id": 1,
        "date": "2021-06-10T10:26:00.117830+03:00",
        "name": "zcanfes",
        "location": "Rome",
        "tag": "happy",
        "title": "First Day in Rome",
        "story": "This was my first day in Rome after moving. I got out of my house and start walking towards the city center. Everywhere there are buildings to see and admire. Just being in the city can take you back in time to experience the old Rome. Today I plan to visit the Colosseum and I can't wait to see one of the most important monuments in Rome. I will take a lot of pictures and share my story here.",
        "notifyAdmin": false,
        "latitude": 41.9027835,
        "longitude": 12.4963655
    },
    {
        "id": 15,
        "date": "2021-06-10T11:01:29.359460+03:00",
        "name": "melih",
        "location": "Berlin",
        "tag": "happy",
        "title": "Berlin Fever",
        "story": "We finally arrived at Berlin Brandenburg Airport earlier today. I'm pretty thrilled about everything about Berlin. Upon arrival I've eaten some Currywurst and I plan on visiting Mustafa's Gemüse Kebap, a well-known Döner stand. I hope my stay in Berlin will be as awesome as I hope it to be.",
        "notifyAdmin": false,
        "latitude": 52.52000659999999,
        "longitude": 13.404954
    },
    {
        "id": 16,
        "date": "2021-06-10T15:15:35.250653+03:00",
        "name": "melihaydogd",
        "location": "Boğaziçi Kuzey Kampüs İstanbul",
        "tag": "happy",
        "title": "The End of Online Education",
        "story": "Finally, the new semester is started and it is offline. I was bored of staying at home and looking at the computer every morning. It is nice to see people around. The most important thing is that my university club is finally active again. The club's name is aviation club and the main activity of the club is paragliding.",
        "notifyAdmin": false,
        "latitude": 41.0863067,
        "longitude": 29.0441352
    },
    {
        "id": 17,
        "date": "2021-06-10T15:26:52.016106+03:00",
        "name": "mertlkn",
        "location": "Boğaziçi Güney Kampüs",
        "tag": "great",
        "title": "Last Time",
        "story": "It was last year. Not too long ago I guess. I was at the campus. Sun was rising, weather was great. When I sit on the grass in South Campus, I would always forget my worries. That time was no different. But little did I know that was the last one for a long time.",
        "notifyAdmin": false,
        "latitude": 41.0847571,
        "longitude": 29.0510399
    },
    {
        "id": 18,
        "date": "2021-06-10T16:22:22.877351+03:00",
        "name": "kadirkalkan",
        "location": "London",
        "tag": "happy",
        "title": "Awesome London",
        "story": "Finally we arrived at London City Airport. Today we plan to visit the British Museum. We are very excited about this trip because this museum contains more than 13 million artifacts from the ancient world. Here I will share many stories about this trip.",
        "notifyAdmin": false,
        "latitude": 51.5073509,
        "longitude": -0.1277583
    },
    {
        "id": 19,
        "date": "2021-06-10T16:43:30.185647+03:00",
        "name": "nulke",
        "location": "Kremlin Palace",
        "tag": "Art",
        "title": "My First Trip",
        "story": "I made my first touristic trip to the Grand Kremlin Palace with my family. Even as a child, I was fascinated by the beauty of this building. It should come as no surprise that I became an architect years later!",
        "notifyAdmin": false,
        "latitude": 55.7505944,
        "longitude": 37.6153441
    },
    {
        "id": 20,
        "date": "2021-06-10T18:01:27.148807+03:00",
        "name": "omarr09",
        "location": "Belgrad Ormanı",
        "tag": "programming",
        "title": "Programming with the Nature",
        "story": "Today I did something very interesting. I went to the Belgrad Forest with my laptop, and programmed for my project there until my laptop runs out of battery. Vibrant nature and fresh air helped me focus on my code and stay relaxed, but I experienced connection problems sometimes. It was a nice experience overall.",
        "notifyAdmin": false,
        "latitude": 41.184012,
        "longitude": 28.9885021
    },
    {
        "id": 21,
        "date": "2021-06-10T19:05:01.060191+03:00",
        "name": "asenturk",
        "location": "Adelaide",
        "tag": "Happy",
        "title": "A Beginning",
        "story": "After more than 20 hours above the sky, I finally arrive to the place where I’ll spend 7 months of my time on earth. Nervous, happy, free, anxious, worry, cheerful… I know that I’ll return home after this period, but I’m worried about something else, “What if I like this place more than home?”. Random people smile and wave me at the streets of Adelaide, should I wave them back?",
        "notifyAdmin": false,
        "latitude": -34.9284989,
        "longitude": 138.6007456
    },
    {
        "id": 22,
        "date": "2021-06-10T20:55:35.697301+03:00",
        "name": "Tough guy (mecolak)",
        "location": "Boğaziçi University",
        "tag": "happy",
        "title": "About my School",
        "story": "This is my school. There are many like it, but this one is mine.\r\nMy school is my best friend. It is my life. I must master it as I must master my life.\r\nWithout me, my school is useless. Without my school, I am useless. So... I will beat anyone who doesn't like my school (in a not literal sense).",
        "notifyAdmin": true,
        "latitude": 41.0847571,
        "longitude": 29.0510399
    },
    {
        "id": 23,
        "date": "2021-06-11T02:16:56.077359+03:00",
        "name": "uskudarli",
        "location": "Bangalore",
        "tag": "the heart understands",
        "title": "Understading without words",
        "story": "On this hot day in Bangalore I was with a woman who was excitedly telling me something. I could not understand as I don't speak Kannada. And, she didn't speak any English. But she was clearly really trying to tell me something. Which made me intent on trying to understand. Eventually, I did. I have no idea how. And, her story, well it is private. And it is sad. I remain touched that the communication had happened and also mystified as to how it did.",
        "notifyAdmin": false,
        "latitude": 12.9715987,
        "longitude": 77.5945627
    },
    {
        "id": 24,
        "date": "2021-06-12T10:51:35.023453+03:00",
        "name": "zcanfes",
        "location": "Istanbul",
        "tag": "programming",
        "title": "Internship Day",
        "story": "This was the last day of my internship. I have opened my last Pull Request and merged with the main branch. I have completed all my tasks and issues. I have learned a lot of things during this internship and improved my programming skills. But most importantly, I have made a lot of great and new friends and I wish to see them soon again.",
        "notifyAdmin": false,
        "latitude": 41.0082376,
        "longitude": 28.9783589
    }
]
Get a Specific Story

This returns a Story object that is stored in the database.

Request:

Parameters:

{
    story_id = The id of the story
}

Response:

  • "GET /api/story/1/ HTTP/1.1" 200

Data:

{
    "id": 1,
    "date": "2021-06-10T10:26:00.117830+03:00",
    "name": "zcanfes",
    "location": "Rome",
    "tag": "happy",
    "title": "First Day in Rome",
    "story": "This was my first day in Rome after moving. I got out of my house and start walking towards the city center. Everywhere there are buildings to see and admire. Just being in the city can take you back in time to experience the old Rome. Today I plan to visit the Colosseum and I can't wait to see one of the most important monuments in Rome. I will take a lot of pictures and share my story here.",
    "notifyAdmin": false,
    "latitude": 41.9027835,
    "longitude": 12.4963655
}
Update a Specific Story

This updates a Story object that is stored in the database.

Request:

Parameters:

{
    story_id = The id of the story
    data = A JSON object that has updated information.
}

An example for clarification:

{
    story_id = 16,
    data = {
             "name": "melihaydogd",
             "location": "Boğaziçi Kuzey Kampüs İstanbul",
             "tag": "Happy",
             "title": "The End of Online Education",
             "story": "Finally, the new semester is started and it is offline. I was bored of staying at home and looking at the computer every morning. It is nice to see people around. The most important thing is that my university club is finally active again. The club's name is aviation club and the main activity of the club is paragliding."
    }
}

Response:

  • "PUT /api/story/1/ HTTP/1.1" 200

Data:

  • This returns the Story object with updated information.
Delete a Specific Story

This deletes a Story object that is stored in the database.

Request:

Parameters:

{
    story_id = The id of the story
}

Response:

  • "DELETE/api/story/1/ HTTP/1.1" 200

Data:

  • This returns nothing because the Story object is deleted.

3. Errors and Successes

Name Meaning
HTTP_200_OK GET operation is successful.
HTTP_200_OK UPDATE operation is successful.
HTTP_204_OK DELETE operation is successful.
HTTP_400_BAD_REQUEST Unexpected type is entered as value. This can happen if a random string is entered as a location. In this case, coordinates cannot be found.
HTTP_404_NOT_FOUND No such story is found in the database.

4. Code Documentation

Model Used in Story API
  • Stories are stored in the database. Django creates a table for models automatically so this story model is created. Only name, location, tag, title, and story are wanted to post a story. However, more fields are stored in the database and they can be seen in the following class.

  • longitude and latitude are gotten from Google Maps API after posting the story.

  • date is automatically entered by the database.

  • notifyAdmin is entered by using Tisane API after posting the story.

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
Serializer
  • The following serializer is used for serializing a Story object. notifyAdmin, latitute, and longitude fields are not supposed to be filled by the user. Thus, these fields are read-only fields. When making a GET API call for a story, all of the fields will be returned. However, when making a POST API call, read-only fields cannot be filled by users.

  • id field is not a part of the Story model because it is provided by the database. It is the primary key for the Story table and automatically increments.

class StorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Story
        fields = ['id' ,'date', 'name', 'location', 'tag', 'title', 'story', 'notifyAdmin','latitude', 'longitude']
        read_only_fields = ['notifyAdmin','latitude', 'longitude']
Views
  • api/story/ endpoint calls get() function of following class. It returns all of the stories in the database as an array. If there is no story in the database, it returns an empty array.

  • This class inherits GenericAPIView from Django REST Framework. GenericAPIView and serializers work together. By using the StorySerializer the Story objects can be easily converted to JSON Objects. Another advantage of them is that they make endpoints visible on Swagger UI. Also, it shows the example response data on Swagger UI. Without using these features, example response data is not shown on Swagger UI because the example response data gets the information from the serializer through GenericAPIView. You can check it from here. You should click on /story/ endpoint to see example response data.

  • In this class, Story.objects.all() function makes a connection with the database and gets all the stories. Then, by using StorySerializer converts all the stories into an array of JSON Objects.

  • If name query parameter is specified in the URL, it will return the stories that match with name field. Example usage:

class StoryList(GenericAPIView):
    """
    List stories.
    """ 
    queryset = Story.objects.all()
    serializer_class = StorySerializer

    def get(self, request, format=None):
        query_params = self.request.query_params
        if len(query_params) == 0:
            stories = Story.objects.all()     
        else:
            name = dict(query_params)['name'][0]
            stories = Story.objects.filter(name=name).all()
        serializer = StorySerializer(stories, many=True)
        return Response(serializer.data)
  • api/story/<int:story_id>/ endpoint calls the get() function of the following class. If there is no such story, it will give 404 NOT FOUND error. Additionally, by using this endpoint, users can use PUT and DELETE API functionalities.

  • Users can update their stories by using api/story/<int:story_id>/ endpoint and PUT functionality. If there is a change in the location, the coordinates of the location will be updated as well.

  • Users also can delete their stories by using api/story/<int:story_id>/ endpoint and DELETE functionality.

class StoryListDetail(GenericAPIView):
    """
    Retrieve, update or delete a story instance.
    """
    queryset = Story.objects.all()
    serializer_class = StorySerializer
    def get_object(self, pk):
        try:
            return Story.objects.get(pk=pk)
        except Story.DoesNotExist:
            raise Http404

    def get(self, request, pk, format=None):
        story = self.get_object(pk)
        serializer = StorySerializer(story)
        return Response(serializer.data)

    def put(self, request, pk, format=None):
        story = self.get_object(pk)
        serializer = StorySerializer(story, data=request.data)
        if serializer.is_valid():
            if story.location != serializer.validated_data['location']:
                lat, lng = find_coordinates(serializer.validated_data['location'])
                serializer.save(latitude=lat, longitude=lng)
            else:
                serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk, format=None):
        story = self.get_object(pk)
        story.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)
Helper Function
  • This function is used for finding coordinates of a location. It is also used in Post Story API functionality. It takes location of a story as a parameter and returns the latitude and longitude of the location.
def find_coordinates(location):
    location = location.split(' ')
    location = "+".join(location)
    url = "https://maps.googleapis.com/maps/api/geocode/json"
    params = {"address":location, "key":env('GOOGLE_MAPS_API_KEY')}
    new_url = create_url(url,params)
    response = requests.get(new_url)
    json_data = json.loads(response.content)
    location = json_data['results'][0]['geometry']['location']
    return location['lat'], location['lng']
Tests
  • URLs of the Story API are tested to see whether the intended URLs are working correctly.
class TestUrls(TestCase):

    def test_list_story_url_resolve(self):
        url = reverse('list_story')
        self.assertEqual(resolve(url).func.view_class, view_locationAPI.StoryList)
        
    def test_detail_story_url_resolve(self):
        url = reverse('detail_story', args=[1])
        self.assertEqual(resolve(url).func.view_class, view_locationAPI.StoryListDetail)
  • Views of the Location API are tested to see whether they return the expected values and HTTP status codes.
class TestViews(TestCase):

    def setUp(self):
        self.client = Client()
        self.mock_data = {"name":"melih",
                          "location":"Karaköy",
                          "tag":"Chilling",
                          "title":"Kahve",
                          "story":"Kahve Keyfi"}
        self.mock_data_1 = view_locationAPI.Story.objects.create(id = 1,
                                title = "Çay",
                               story="Çay keyfi",
                               name ="emre",
                               longitude=10,
                               latitude=20,
                               location="Beşiktaş",
                               tag="mutlu")

    def test_story_list_GET(self):
        response = self.client.get(reverse('list_story'))
        self.assertEqual(response.status_code, 200)
        self.assertEqual(json.loads(response.content)[0]['name'], 'emre')

    def test_story_GET_detail_1(self):
        response = self.client.get(reverse('detail_story', args = [2]))
        self.assertEqual(response.status_code, 404)

    def test_story_GET_detail_2(self):
        response = self.client.get(reverse('detail_story', args = [1]))
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data['name'], 'emre')

    def test_story_PUT_detail(self):
        data = self.client.get(reverse('detail_story', args = [1]))
        data.data['name'] = 'kaan'
        response = self.client.put(reverse('detail_story', args = [1]), data.data, content_type='application/json')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data['name'], 'kaan')

    def test_story_DELETE_detail(self):
        data = self.client.get(reverse('detail_story', args = [1]))
        response = self.client.delete(reverse('detail_story', args = [1]), data.data, content_type='application/json')
        self.assertEqual(response.status_code, 204)
  • Story model is tested to see whether models are created correctly.
class TestModels(TestCase):

    def setUp(self):
        self.mock_data = view_locationAPI.Story.objects.create(title = "Çay",
                               story="Çay keyfi",
                               name ="emre",
                               longitude=10,
                               latitude=20,
                               location="Beşiktaş",
                               tag="mutlu")

    def test_story_model(self):
        self.assertNotEqual(self.mock_data.id, None)
        self.assertEqual(self.mock_data.id, 1)
        

🏠 Home

👪 Team Members

💬 Communication

📈 Effort Tracking

🌐 Postory

🤸‍♀️ Practice App

📆 Project Plans & Milestone Reports

💻 Project Documentation

📝 Regular Meeting Reports

CMPE 352 Meetings

CMPE 451 Meetings

Android Meetings

Backend Meetings

Frontend Meetings

🔍 Research

📎 Templates

Clone this wiki locally