9 minutes read

Making an interactive service means that your users can receive and send information. We use GET requests to get a page and POST, PUT, PATCH, DELETE requests to update data on the server side. It's not a requirement, but a rule to follow. You can still make a valid application if you break it, but its behavior would be unexpected for its users and developers alike.

Let's look closer at how we can exchange data and make the first POST and DELETE request handlers!

Query parameters

I have a dream. Many of us do, and sharing dreams and desires with others is a thing. Maybe if you share your wish with the right person, they can make it come true. Creating a wishlist is a simple way to do that. Ideally, the service should allow users to search wishes by keywords, create new wishes and delete those that are not relevant to them anymore.

We want the service to perform a variety of actions, so how do you tell apart these different requests? Would their only distinctive feature be their HTTP methods? Well, to start processing any request we should look at its method. The URL for different types may stay the same while the action of the server would be different. We use only one URL for each user: <address of server>/<username>. If the hostname is www.makeawish.happen and the username is Mymble, the full address to Mymble's wishlist will be www.makeawish.happen/Mymble.

To search, we would use a GET request. For example, searching by keyword may look like www.makeawish.happen/Mymble?q=cake. We use a query string after the question mark to specify the parameters and their values. To access these values in a request handler, we can use the GET attribute of the HttpRequest class. Query parameters are associated with GET requests, so we can find them in this attribute.

The q parameter in the GET attribute can now be accessed as a key in a dictionary because this attribute is a QueryDict instance. QueryDict is a subclass of Python's dictionary, but the main difference is that QueryDict is immutable; you can't modify the user's request, which makes sense.

Request body

All requests have a body. Sometimes it's empty, but for POST requests, it's filled with data. In these cases, you can find what the client sent to the server in the POST attribute of the HttpRequest class.

How does a client create the request body? You cannot find it in the URL, but you can pass it in an HTML form. Assume that we have the following form:

<form action="/Mymble" method="post">
  {% csrf_token %}
  <input name="wish">
  <button type="submit">Add a wish</button>
</form>

The wish parameter is stored in the POST attribute. This attribute is also a QueryDict instance, so use it as a usual dictionary; still, do not change anything in it, you will get an AttributeError exception. Let us move on to making a handler and processing a new wish.

We have only GET and POST attributes in the HttpRequest class. Even though we have other HTTP methods, there are no other attributes with their names in an instance of this class.

To make this example work, create the WishForm class in the forms.py file and an HTML template with the name form_template.html.

As we use the username in data processing, we assume to already have authorization functionality created. Let's pretend it's all setup and examine the way it might work.

POST request

Making a wish is the first step in bringing it to life. We use the POST handler to save a wish in the application; the signature of a method is similar to the GET handler. The lists are stored only in a static attribute of a class — let's see an example of views.py:

from collections import defaultdict

from django.shortcuts import redirect
from django.views import View

class WishListView(View):
   form_class = WishForm
   initial = {'key': 'value'}
   template_name = 'form_template.html'
 
   def get(self, request, *args, **kwargs):
       form = self.form_class(initial=self.initial)
       return render(request, self.template_name, {'form': form})
 
   wishlist = defaultdict(list)
 
   def post(self, request, *args, **kwargs):
      author = 'Anonymous'
      if request.user.is_authenticated:
           author = request.user.username
      form = self.form_class(request.POST)
      if form.is_valid():
           wish = form.cleaned_data['wish']
           self.wishlist[author].append(wish)
           return redirect('/')

request.user.is_authenticated is the request attribute that represents the current user and whether they are authenticated. The wishes are grouped by the author; if a user is not authorized, their wish goes to the Anonymous group. When a wish is saved, the handler redirects the client to the / page, which is usually the main page of the application. Redirection is a simple mechanism to prevent double sending modification requests when the user refreshes the page or clicks the form submit button several times. Assume the user sends data and refreshes the page the very next moment. Refreshing repeats the action and sends data again. Redirection is not a 100% mechanism to prevent multiple sending, but it's ok for things like wishlists.

Django matches the HTTP method with the same-name method in the View class. With this type of inheritance, only the implemented methods are allowed in requests. For example, if there's no GET method for a given link, the answer to such a request would be 405 Method Not Allowed. In this example, we've added the GET method, which shows us the form on a page in case we don't post anything at the moment.

DELETE request

Fleeting wishes come and go; when they go, our user would probably want to remove them from the wishlist. The most appropriate method for this task is DELETE.

We add the delete handler to the same class and use the same link for different requests. As we said, it's valid to make requests with different HTTP methods to the same link, and the type of method defines which handler will process the request.

from collections import defaultdict

from django.shortcuts import redirect, Http404
from django.views import View


class WishListView(View):
    wishlist = defaultdict(list)
 
    def delete(self, request, wish, *args, **kwargs):
        author = 'Anonymous'
        if request.user.is_authenticated:
            author = request.user.username
 
        if wish in self.wishlist[author]:
            self.wishlist[author].remove(wish)
        else:
             raise Http404
        return redirect('/')
 
    def get(self, request, *args, **kwargs):

    def post(self, request, *args, **kwargs):
        ...

We cannot use the POST attribute of the HttpRequest instance: it contains the data only for POST requests, so we pass a wish in a query to define what to remove.

You can define the named parameters like the one above with urlpatterns.

Users are redirected again, but this time it's not obligatory. It's impossible to delete the same object twice, so it's safe to call this method several times in a row, as the object is already removed after the first call. The property of an operation to be applied several times without changing the result beyond the initial call is called idempotency.

DELETE, PUT, and GET methods are idempotent while POST is not.

Conclusion

If you didn't make your wishlist yet, it might be interesting to do so, but you can also go ahead and make an entire wishlist service for you and other folks out there. You can customize the pages, implement all HTTP methods and invite your friends to post their wishes and share them with the world (wide web).

4 learners liked this piece of theory. 7 didn't like it. What about you?
Report a typo