How I Built a Real Estate Application Using the Django Web Framework

4/5 - (2 votes)

When you began your programming journey with Python, you created so many projects that helped you become acquainted with Python syntax including how to work with lists, strings, tuples, dictionaries and so on.

As your knowledge of Python increases, you realized the need to choose an area of specialty where the application of Python can be further used. You chose web development.

Many Python frameworks can be used to build complex web applications, the popular ones being Django, Flask and FastAPI.

βš”οΈ Recommended: Flask vs Django: Comparing the Two Most Popular Python Web Frameworks

So, you started to build projects using these frameworks to further strengthen your knowledge of web development with Python.

This is actually how I began my journey as a self-taught programmer. The portfolio website, blog application, and so many other projects created with these frameworks have greatly helped me to improve my knowledge of Python programming and its usage in web development.

By sharing what I learned in project articles such as this, it not only helps me to retain such knowledge, it also helps others like you because we are on this journey together.

In this tutorial, I will show you how I built a real estate application using the Django web framework.

This project will span three series. In this series, we will create the real estate application and have it running on the local server. In the second part, we will add a user authentication feature. The final series will have a contact form added for registered users requesting more information about the property.

This project tutorial is for those already familiar with Django, including its installation process. If you are learning Django for the first time, you are required to go through any or all of the above projects designed with Django. For this project, create the app name, listings, in your current directory unless you know what you are doing.

The Model

The purpose of a real estate application is to allow users such as landlords, buyers, sellers, investors, or realtors to search for information about properties. This information will be stored in our database. So, we will create a simple model for this database.

In your models.py file, type the following:

from django.db import models


class Listing(models.Model):
    name = models.CharField(max_length=100)
    price = models.IntegerField()
    n_bedrooms = models.IntegerField(null=True)
    n_bathrooms = models.IntegerField(null=True)
    sqft = models.IntegerField()
    image = models.ImageField(upload_to='images')
    address = models.CharField(max_length=100)


    def __str__(self):
        return self.title

The model contains information typically seen in a real estate application. The name of the property, number of bedrooms and bathrooms, image of the property, square footage, address, and amount of the property.

You may want to add a TextField for a description of the property.

Run the migration command to reflect these changes in the database. Make sure you install the Pillow module since we will be uploading images.

Configurations

Next, go to the settings.py file and add these:

MEDIA_URL = 'media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

Also, add these to 'DIRS' value under the TEMPLATES section.

'DIRS': [os.path.join(BASE_DIR, 'templates')],

Then, go to the project-level URL, and add these too.

from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static


urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('listings.urls')),
]+static(settings.MEDIA_URL,document_root = settings.MEDIA_ROOT)

We specified that images will go to the media folder when uploaded. Then, inside the media folder, another folder, images, will be created to keep them. This is also what we specified in the image field of the Listing model.

The Form

Aside from using the admin, the form is another way to add property details to the database. We will use Django built-in forms to reduce the amount of code to write. Create a forms.py file in your listings folder.

from .models import Listing
from django import forms


class ListingForm(forms.ModelForm):
    class Meta:
        model = Listing
        fields = '__all__'

This will display all fields in the database. Later, we will import django-crispy-forms to style the forms.

The Views

We will use function-based views for the view functions.

from django.shortcuts import render, redirect,
from .models import Listing
from .forms import ListingForm


def list(request):
    listings = Listing.objects.all()
    context = {
        'listings': listings
    }
    return render(request, 'lists.html', context)


def retrieve(request, pk):
    listing = Listing.objects.get(id=pk)
    context = {
        'listing': listing
    }
    return render(request, 'list.html', context)

We query the database in these view functions. The first view function simply returns all the property listings in the database while the second returns a property based on the primary key.

def create(request):
    form = ListingForm()
    if request.method == 'POST':
        form = ListingForm(request.POST, request.FILES)
        if form.is_valid():
             form.save()
             return redirect('/')

    context = {
        'form': form
    }
    return render(request, 'create.html', context)

This view function receives input from users provided the request method is POST, checks whether it is valid before saving the form and redirects the user to the homepage. Otherwise, the request method is GET. In that case, it simply displays an empty form for the user.

The request.FILES tells Django that a file is to be uploaded. The file is the property image.

def update(request, pk):
    listing = Listing.objects.get(id=pk)
    form = ListingForm(instance=listing)

    if request.method == 'POST':
        form = ListingForm(request.POST, instance=listing, files=request.FILES)
        if form.is_valid():
            form.save()
            return redirect('/')

    context = {
        'form': form
    }
    return render(request, 'update.html', context)

To update a property detail, we query the database to get the property detail we want to update (which is based on the primary key). When we call the ListingForm class, we pass in the property detail to the instance parameter so that when we open the web page, we can see the details we want to update.

Since we are updating the property details, it means some information will be inputted from the user. So, we pass in a POST request including request.FILES just in case an image is to be updated.

def delete(request, pk):
    listing = Listing.objects.get(id=pk)
    listing.delete()
    return redirect('/')

To delete a property detail, we simply pass in the particular property detail (based on the primary key), and use the .delete() method to delete it. Afterward, we redirect the user to the homepage. As simple as that.

The URLs

Let’s define the URL endpoints for the view functions. Create a urls.py file in the listings folder.

from django.urls import path
from .views import ( lists, list, update,
                              create, delete )


urlpatterns = [
    path('', lists, name='lists'),
    path('listings/<pk>/', list, name='list'),
    path('create_listing/', create, name='create'),
    path('listings/<pk>/edit/', update, name='update'),
    path('listings/<pk>/delete/', delete, name='delete),
]

It’s a good practice to specify the name parameter whether you will use it or not. It is a way to refer to the endpoints in your templates when it becomes necessary to do so.

The Templates

Create the templates folder for the template files. As usual, we will create a base.html that other templates will inherit.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://cdn.tailwindcss.com"></script>
  </head>
  <body>
    <div class="mx-auto max-w-5xl px-4 py-4">
      {% block content %}
      {% endblock content %}
    </div>
    <script src="" async defer></script>
  </body>
</html>

We use Tailwind to style the web page. Every other template file inheriting this base.html will be added inside the {% block content %} block. The async and defer keywords ensure that scripts are loaded immediately they are available and in the correct order.

Import django-crispy-forms and add it to the INSTALLED_APPS. Since we are using Tailwind to style the webpages, we have to install crispy-tailwind, and also add it to the INSTALLED_APPS.

The section should be like this:

INSTALLED_APPS = [
…
    # custom app
    'listings',
    # third-party apps
    'crispy_forms',
    'crispy_tailwind',
]

Add the following to the settings.py file:

CRISPY_ALLOWED_TEMPLATE_PACKS = 'tailwind'
CRISPY_TEMPLATE_PACK = 'tailwind'

Next is the lists.html

{% extends "base.html" %}

{% block content %}

<h3><a href="{% url 'create' %}" class="text-blue-500">CREATE</a></h3>

{% for listing in listings %}

<div class="mt-2 px-3 py-2 bg-gray-100 rounded-sm">
    <h3 class="text-3xl text-gray-900">{{ listing.name }}</h3>
    <img src="{{ listing.image.url }}" alt="" />
    <a href="{% url 'retrieve' listing.pk %}" class="text-blue-500">
        READ
    </a>
    <a href="{% url 'update' listing.pk %}" class="text-blue-500">
        UPDATE
    </a>
    <a href="{% url 'delete'  listing.pk %}" class="text-blue-500">
        DELETE
    </a>
</div>

{% endfor %}

{% endblock content %}

We loop through all the property listings and display them. Notice how specifying the name for the endpoints make it possible to refer to them in the templates. This makes our code short and readable.

Next is create.html

{% extends "base.html" %}
{% load tailwind_filters %}
{% block content %}

<h1 class="text-4xl text-gray-900">Create Property Details</h1>
<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    {{ form | crispy }}
    <button type="submit">Submit</button>
</form>

{% endblock %}

We make sure we secure the form using csrf-token. With the crispy-tailwind loaded, we just include it in the form like this: {{ form | crispy }}.

Next is update.html which is similar to the previous one.

{% extends "base.html" %}
{% load crispy_forms_tags %}

{% block content %}

<h1 class="text-4xl text-gray-900">Update Property Details</h1>
<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    {{ form | crispy }}
    <button type="submit">Submit</button>
</form>

{% endblock %}

Finally, the retrieve.html

{% extends "base.html" %}

{% block content %}

<div class="px-3 py-2 bg-gray-100 rounded-sm">
    <h3 class="text-3xl text-gray-900">{{ listing.name }}</h3>
    <img src="{{ listing.image.url }}">
    <p class="text-gray-900">${{ listing.price }}</p>
    <p class="text-gray-600 text-sm">{{ listing.address }}</p>

    <div class="mt-5 py-5 border-t border-gray-200">
        <a href="{% url 'update' listing.pk %}" class="text-blue-500">
            Edit
        </a>
        <a href="{% url 'delete' listing.pk %}" class="ml-3 text-blue-500">
            Delete
        </a>
         <a href="{% url 'home' %}" class="ml-3 text-blue-500">
            Home
        </a>
    </div>

</div>

{% endblock %}

Once the delete link is clicked, the property detail is deleted without asking. We will correct that in the next series.

Conclusion

We have successfully created a real estate application using the Django framework. Here is the source code. Please note that the property details listed are just for demonstration purposes. The pictures are taken from Zillow website. You may use their listings to populate your database.

You can check out our GitHub here.

In the next series, we will add a user authentication feature to ensure anyone making inquiries using a contact form must be registered. Stay Tuned!