DJANGO HTMX LOAD MORE OBJECTS
Tags: python, django, htmx, javascript
Published: 2022-11-21 09:11:42

In this guide, we'll be using Django and HTMX to load more objects from the database without refreshing our page. Traditionally, adding a load more button which sends a request to the database to load more data is done with JavaScript. But now we can load more data by writing little to no JavaScript for this purpose.

First, we need to set up our Django project by first creating a virtual environment and installing the necessary dependencies. 


**CREATING A VIRTUAL ENVIRONMENT**
To create a virtual environment on your windows machine, run the command below;

```
python -m venv my_project
```
Next, we need to change the directory into the newly created virtual environment with the command below;

```
cd my_project
```
Now we need to activate the virtual environment with the command below;

```
scripts\activate
```

We are done with creating our virtual environment and can now continue to start our new project by first creating the project with the name "blog". But before we do that, we need to first install Django which will be used and also going to help us create a Django project easily.

```
pip install Django
```

After installing Django, we now need to start our new Django blog project with the command below;

```
django-admin startproject blog
```


After starting our project, we now need to change the directory into this newly created blog project with the command below;

```
cd blog
```

We're all set and can now test our project is ready by running the server. To run the server, use the command below;

```
python manage.py runserver
```

Now our server is running, we can now go to our local host on port 8000 to confirm Django is running. 127.0.0.1:8000

Congratulations, we're now up and running and have successfully started our new Django project in a virtual environment which is now running on port 8000.

Now we need to create a new app to handle all our blog posts. To do this, go back to your command line and kill the server running and then run this command below;


```
python manage.py startapp posts
```

Now our new "posts" app is ready, we can now add the app in our settings.py file. To add it, locate the INSTALLED_APPS and add 'posts ' to the list.

```
INSTALLED_APPS = [
    ...
    'posts',
]
```

Now we can continue by creating our models instance in our posts app. Go to models.py in the posts app and continue with the code below;





```
class Post(models.Model):
    title = models.CharField(max_length=100)
    description = models.CharField(max_length=255)
    body = models.TextField()
    date_created = models.DateTimeField(auto_now_add=True)
    last_updated = models.DateTimeField(auto_now=True)
    def __str__(self):
        return str(self.title)
```

After adding all to the models.py, we need to update our admin.py file in the posts app so we can easily add posts from the easy django admin site. 

```
from django.contrib import admin
from .models import Post

admin.site.register(Post)

```

We are good to go, this is all we need for this tutorial. Now we can proceed to migrate all our data to the database by running the command below;

```
python manage.py makemigrations
python manage.py migrate

```

After migrating our database, we now need to create a django superuser which will manage everything in our django admin panel which includes adding posts to our database. Run the command below to create a superuser;


```
python manage.py createsuperuser

```

Fill in your credentials and then your superuser can now log into the admin panel which is located at (http://127.0.0.1:8000/admin/). First you need to run the server again to access the django admin panel.
Login to the django admin panel and add at least 20 blog posts for our testing purpose.

We now have up to 20 blog posts so we can start doing all we have to do in loading our posts on our site. First, we need to go to the posts app and start writing our home view to load all our posts.


```
from django.shortcuts import render
from .models import Post


def home(request):
    posts = Post.objects.all()
    context = {
        'posts': posts,
    }
    return render(request, 'posts/all-posts.html', context)

```


Breakdown of our views.py
first line imports render which returns a HttpResponse

The second line imports our Post models objects which return all the posts we added to the database initially.

Our function is the views that our URLs will call and render. This function also adds our blog posts to the context which can be called in the Django template language

This is all we need in our views, for now, we can now proceed to add our URLs so Django will display our posts in the root URL. To do this, create a url.py file in the posts app and add the code below.


```
from django.urls import path
from .views import home

app_name = 'home'

urlpatterns = [
    path('', home, name='home')
]

```


Now we need to add this URL in the root URLs. We now need to go to blog/urls.py and update it to be like this code below;


```
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('posts.urls', namespace='posts'))
]

```


Now we can run the server and visit http://127.0.0.1:8000. Now you'll see we have an error which says 
TemplateDoesNotExist at /posts/all-posts.html

This is a friendly error for us in this case and it shows we have all things in place. Next, we'll need to create a templates folder in our root directory and a posts folder in the newly created templates folder. In this posts folder, we need to add our home.html file. This setup is based on our views.py where we have our home.html setup. 
Once this is in place, we need to go to our settings.py file and instruct Django where to find our Html files.


```
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        ...

```

Here we updated the DIRS with the value; 
'DIRS': [BASE_DIR / 'templates'],

This is all we need for our templates setup, we'll add our html files soon.

Next we need to install htmx with the command below;


```
pip install django-htmx
```

After installing htmx, we now need to add htmx to our installed apps;

```
...
'django_htmx',
...

```

Next is to add htmx to our middleware

```
...
'django_htmx.middleware.HtmxMiddleware',
...

```


We're all ready to start using htmx in our django project. First we have to go to views.py and update our views with the code below;


```
from django.shortcuts import render
from .models import Post
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator


def home(request):
    posts = _load_posts(request)
    context = {"posts": posts,}
    return render(request, "posts/all-posts.html", context)


def list_load_posts_view(request):
    posts = _load_posts(request)
    context = {"posts": posts,}
    return render(request, "posts/partials/all-posts.html", context)


def _load_posts(request):
    page = request.GET.get("page")
    posts = Post.objects.all().order_by('-date_created')
    paginator = Paginator(posts, 3)
    try:
        posts = paginator.page(page)
    except PageNotAnInteger:
        posts = paginator.page(1)
    except EmptyPage:
        posts = paginator.page(paginator.num_pages)
    return posts
```


We're all good to go, our last steps will include creating a urls.py file in our posts app and update with the code below


```
from django.urls import path
from .views import home, list_load_posts_view

app_name = 'home'

urlpatterns = [
    path('', home, name='home'),
    path('posts/', list_load_posts_view, name='posts'),
]
```

Now we have all our setup, we can now add our html files. We need two html files, one is the main home page view while the second will be included from our htmx view from our partials folder.

Now if we go back to our root url, we'll see a django error html page. This shows we're on the right track and can now update our all-posts.html file with the code below.


```
<!doctype html>
<html lang="en">

<head>
  <!-- Required meta tags -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <!-- Bootstrap CSS -->
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
    integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

  <title>Hello, world!</title>

  <!-- HTMX -->
  <script src="https://unpkg.com/htmx.org@1.6.0"></script>
  <script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
</head>

<body>

  <style>
    .centralize-div {
      display: flex !important;
      justify-content: center !important;
    }
  </style>

  <nav class="navbar navbar-expand-lg navbar-light bg-light">
    <div class="container-fluid">
      <a class="navbar-brand" href="#">BLOG</a>
      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
        aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarSupportedContent">
        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
          <li class="nav-item">
            <a class="nav-link active" aria-current="page" href="#">Posts</a>
          </li>
        </ul>
      </div>
    </div>
  </nav>

  <div class="container py-5">
    <div class="row">
      {% include 'posts/partials/all-posts.html' %}
    </div>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
    integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous">
    </script>

</body>
</html>
```


If we go through our code above, you can see we have a basic bootstrap navigation bar, and all our posts from the database. 

Lastly, we need to create a folder named "partials" in the posts templates and add the just included html file "all-posts.html". Add the code below to the htmx partials file;

```
{% if posts %}
{% for post in posts %}
<div class="col-md-4 centralize-div">
  <div class="card m-2 p-2" style="width: 18rem;">
    <div class="card-body">
      <h5 class="card-title">{{ post.title }}</h5>
      <p class="card-text">
        {{ post.body|truncatechars:50 }}
      </p>
      <!-- <a href="#" class="btn btn-primary">Visit</a> -->
    </div>
  </div>
</div>
{% endfor %}
{% else %}
<div class="centralize-div">
  <h3 class="ui header center aligned">No results!</h3>
</div>
{% endif %}


<div class="centralize-div py-5" id="load-more">
  {% if posts.has_next %}
  <div class="ui divider"></div>
  <button class="btn btn-primary" hx-get="{% url 'posts:posts' %}"
    hx-vals='{"page": "{{ posts.next_page_number }}", "posts": "{{ posts }}"}' hx-target="#load-more"
    hx-swap="outerHTML">
    Load more
  </button>
  {% endif %}
</div>
```


Congratulations, we now have a working django project which has the load more feature working fine with less code and working efficiently. You can access the code in the github repo, also feel free to ask your questions and possible suggestions if you find me making the wrong decisions, I appreciate your time and effort.


https://github.com/Arthurobo/django-htmx-load-more-objects