Django model + Frontend CRUD + pagination + API REST and filters by Example
Tags: django, crud, pagination, api
Published: 2021-01-30 21:02:00

## Table Of Contents
  * [Create model](#chapter-1)
  * [Create frontend](#chapter-2)
  * [Create API](#chapter-3)

### Create model <a name="chapter-1"></a>

We type in base dir:
```
python manage.py startapp stocks
```
Register app in settings.py:
```
INSTALLED_APPS = [
    ...
    'stocks',
]
```

We write initial model in "models.py":
```
from django.db import models

class Stock(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=128)
    price = models.FloatField()
    created = models.DateTimeField(auto_now_add=True)
    update = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = "stock"
        verbose_name_plural = "stocks"
        ordering = ['-id', ]

    def __str__(self):
        return self.name
```
Apply migrations:
```
python manage.py makemigrations
python manage.py migrate
```

Create your ModelAdmin:
```
from django.contrib import admin
from django.contrib.admin import register
from stocks.models import Stock


@register(Stock)
class StockAdmin(admin.ModelAdmin):
    list_display = ['id', 'name', 'price', 'update']

```
Then you should be see something like this in your django admin:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/l7b1bscn89mqrvraebzl.PNG)

### Create frontend <a name="chapter-2"></a>

Create a ModelForm for after render it. Create new file "forms.py".
```
from django import forms
from .models import Stock

class StockForm(forms.ModelForm):
    class Meta:
        model = Stock
        fields = ['name', 'price']
```
Now, we should create a CRUD views in file "views.py" (note , i add staff authentication required in update, create and delete but not is list amd not in detail):
```
from django.urls import reverse_lazy
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from .models import Stock
from .forms import StockForm
from django.utils.decorators import method_decorator
from django.contrib.admin.views.decorators import staff_member_required


class StockListView(ListView):
    model = Stock
    paginate_by = 2  # change it, is page size

    def get_queryset(self):
        query = self.request.GET.get('q')
        if query:
            object_list = self.model.objects.filter(name__icontains=query)
        else:
            object_list = self.model.objects.all()
        return object_list


class StockDetailView(DetailView):
    model = Stock


@method_decorator(staff_member_required, name='dispatch')
class StockCreate(CreateView):
    model = Stock
    form_class = StockForm
    template_name_suffix = '_create_form'
    success_url = reverse_lazy('stocks:list')


@method_decorator(staff_member_required, name='dispatch')
class StockUpdate(UpdateView):
    model = Stock
    form_class = StockForm
    template_name_suffix = '_update_form'

    def get_success_url(self):
        return reverse_lazy('stocks:update', args=(self.object.id, )) + '?ok'


@method_decorator(staff_member_required, name='dispatch')
class StockDelete(DeleteView):
    model = Stock
    success_url = reverse_lazy('stocks:list')

```
We connect this views to url paths (create "urls.py"):
```
from django.urls import path
from .views import StockListView, StockDetailView, StockCreate, StockUpdate, StockDelete


stocks_patterns = ([
    path('', StockListView.as_view(), name='list'),
    path('<int:pk>/<slug:slug>/', StockDetailView.as_view(), name='detail'),
    path('create/', StockCreate.as_view(), name='create'),
    path('update/<int:pk>', StockUpdate.as_view(), name='update'),
    path('delete/<int:pk>', StockDelete.as_view(), name='delete'),
], 'stocks')

```
In settings.urls we add:
```
...
from stocks.urls import stocks_patterns
...

urlpatterns = [
    ...
    path('stocks/', include(stocks_patterns)),
    ...
]
```
This CRUD views need next 5 templates:
- stocks/templates/stocks/stock_list.html
- stocks/templates/stocks/stock_detail.html
- stocks/templates/stocks/stock_create_form.html (custom prefix)
- stocks/templates/stocks/stock_update_form.html (custom prefix)
- stocks/templates/stocks/stock_confirm_delete.html

stocks/templates/stocks/stock_list.html:
```
{% extends 'core/base.html' %}
{% load static %}

{% block title %}Stocks{% endblock %}
{% block content %}
    <form method='GET'>
      <div class="columns is-centered mb-1">
        <div class="column is-11">
            <p class="control has-icons-left">
              <input class='input' type='text' name='q' value='{{ request.GET.q }}' placeholder="Search">
              <span class="icon is-left">
                <i class="fas fa-search" aria-hidden="true"></i>
              </span>
            </p>
        </div>

        <div class="column">
          <input class="button is-primary" type='submit' value="Search">
        </div>

      </div>

    </form>

    {% if stock_list|length == 0 %}
        No se han encontrado resultados.
        <a href="{% url 'stocks:create' %}" class="card-footer-item">Create</a>
    {% endif %}

    {% for stock in stock_list %}

      <div class="card">
        <header class="card-header">
          <p class="card-header-title">
            {{ stock.name }}
          </p>
          <a href="#" class="card-header-icon" aria-label="more options">
            <span class="icon">
              <i class="fas fa-angle-down" aria-hidden="true"></i>
            </span>
          </a>
        </header>
        <div class="card-content">
          <div class="content">
            {{stock.name|striptags|safe|truncatechars:"200"}} {{stock.price}}
            <br />
            <time datetime="2016-1-1">{{stock.updated}}</time>
          </div>
        </div>
        <footer class="card-footer">

          {% if request.user.is_staff %}
            <a href="{% url 'stocks:create' %}" class="card-footer-item">Create</a>
            <a href="{% url 'stocks:delete' stock.id %}" class="card-footer-item">Delete</a>
            <a href="{% url 'stocks:update' stock.id %}" class="card-footer-item">Edit</a>
          {% endif %}
          <a href="{% url 'stocks:detail' stock.id stock.name|slugify %}" class="card-footer-item">Read more ...</a>
        </footer>
      </div>

    <br />

    {% endfor %}



    {% if stock_obj.paginator.num_pages > 1 %}
    <nav class="pagination" role="navigation" aria-label="pagination">
      {% if stock_obj.has_previous %}
          <a class="pagination-previous" href="?page={{ stock_obj.previous_page_number }}&q={{ request.GET.q }}">Previous</a>
      {% endif %}
      {% if stock_obj.has_next %}
          <a class="pagination-next" href="?page={{ stock_obj.next_page_number }}&q={{ request.GET.q }}">Next page</a>
      {% endif %}
      <ul class="pagination-list">
        {% if stock_obj.number > 3 %}
          <li>
            <a class="pagination-link" aria-label="Goto page 1" href="?page=1&q={{ request.GET.q }}">1</a>
          </li>
          {% if stock_obj.number > 4 %}
            <li>
              <span class="pagination-ellipsis">&hellip;</span>
            </li>
          {% endif %}
        {% endif %}

        {% for i in stock_obj.paginator.page_range %}
        <li>
          {% with leftmax=stock_obj.number|add:"-3" %}
            {% with rightmax=stock_obj.number|add:"+3" %}
              {% if leftmax < i %}
                {% if i < rightmax %}

                  {% if i == stock_obj.number %}
                    <a class="pagination-link is-current" aria-label="Goto page {{ i }}" href="?page={{ i }}&q={{ request.GET.q }}" aria-current="page">{{ i }}</a>
                  {% else %}
                    <a class="pagination-link" aria-label="Goto page {{ i }}" href="?page={{ i }}&q={{ request.GET.q }}">{{ i }}</a>
                  {% endif %}

                {% endif %}
              {% endif %}
            {% endwith %}
          {% endwith %}
        </li>
        {% endfor %}

        {% with rightdistance=stock_obj.paginator.num_pages|add:"-2" %}
          {% with rightdistanceplus=stock_obj.paginator.num_pages|add:"-3" %}
            {% if stock_obj.number < rightdistance %}
              {% if stock_obj.number < rightdistanceplus %}
                <li>
                  <span class="pagination-ellipsis">&hellip;</span>
                </li>
              {% endif %}
              <li>
                <a class="pagination-link" aria-label="Goto page {{ stock_obj.paginator.num_pages }}" href="?page={{ stock_obj.paginator.num_pages }}&q={{ request.GET.q }}">{{ stock_obj.paginator.num_pages }}</a>
              </li>
            {% endif %}
          {% endwith %}
        {% endwith %}

      </ul>
    </nav>
    {% endif %}

{% endblock %}

```
stocks/templates/stocks/stock_detail.html:
```
{% extends 'core/base.html' %}
{% load static %}
{% block title %}{{object.name}}{% endblock %}
{% block content %}
<main role="main">
  <div class="container">
    <div class="row mt-3">
      <div class="col-md-9 mx-auto">
        <h2 class="title">{{object.name}}</h2>
        <div>
          name: {{object.name|safe}}<br />
          price: {{object.price}}<br />
          {% if request.user.is_staff %}
          <br />
            <p><a href="{% url 'stocks:list' %}">Volver</a></p>
            <p><a href="{% url 'stocks:update' object.id %}">Editar</a></p>
          {% endif %}
        </div>
      </div>
    </div>
  </div>
</main>
{% endblock %}

```
stocks/templates/stocks/stock_create_form.html:
```
{% extends 'core/base.html' %}
{% load bulma_tags %}
{% load static %}

{% block title %}Create{% endblock title %}

{% block content %}
<form method="post">
   {% csrf_token %}
   {{ form | bulma }}
   <div class="field">
     <button type="submit" class="button is-primary">Create</button>
   </div>
   <input type="hidden" name="next" value="{{ next }}"/>
</form>
<p><a href="{% url 'stocks:list' %}">Volver</a></p>
{% endblock content %}


```
stocks/templates/stocks/stock_update_form.html:
```
{% extends 'core/base.html' %}
{% load bulma_tags %}
{% load static %}

{% block title %}Update{% endblock title %}

{% block content %}
{% if 'ok' in request.GET %}
    <article class="message is-primary">
      <div class="message-header">
        <p>Actualizado</p>
        <button class="delete" aria-label="delete"></button>
      </div>
      <div class="message-body">
          Updated. <a href="{% url 'stocks:detail' stock.id stock.name|slugify %}">more details here.</a>
      </div>
    </article>
{% endif %}
<form method="post">
   {% csrf_token %}
   {{ form|bulma }}
   <div class="field">
     <button type="submit" class="button is-primary">Update</button>
   </div>
   <input type="hidden" name="next" value="{{ next }}"/>
</form>
<p><a href="{% url 'stocks:list' %}">Volver</a></p>
{% endblock content %}


```
stocks/templates/stocks/stock_confirm_delete.html:
```
{% extends 'core/base.html' %}
{% load static %}
{% block title %}{{object.name}}{% endblock %}
{% block content %}
<div class="box has-text-centered">
  <form action="" method="post">{% csrf_token %}
      <p class="mb-3">¿Estás seguro de que quieres borrar <b>"{{ object }}"</b>?</p>
      <input class="button is-primary" type="submit" value="Sí, borrar la página" />
      <input class="button is-danger" onclick="history.go(-1); return false;" value="Cancelar" />
  </form>
</div>
{% endblock %}
```
I use 'core/base.html' from django-bulma. Here the result:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/p9ijpub4rz45d40exza8.PNG)

We have search, pagination, edit form, create form and delete. All this, with minimal code and very customizable.

### Create API <a name="chapter-3"></a>

For expose this model to API REST, we use django restframework and django-filter:
```
$ pip install djangorestframework
$ pip install django-filter
```
And register it in settings:
```
INSTALLED_APPS = [
    ...
    'rest_framework',
    'django_filters',
]
```

For convert model to json, or json to model we need create "serializers.py" file, and type:
```
from rest_framework import serializers
from .models import Stock


class StockSerializer(serializers.ModelSerializer):
    class Meta:
        model = Stock
        fields = '__all__'
```
For create a API Rest that let "filter" and "order" for someone field of model. We create "filters.py" and type:
```
import django_filters
from .models import Stock


class StockFilter(django_filters.FilterSet):
    id = django_filters.NumberFilter()
    name = django_filters.CharFilter(lookup_expr='iexact')
    price = django_filters.CharFilter(lookup_expr='eq')
    order = django_filters.OrderingFilter(fields=('name', 'price', ))

    class Meta:
        model = Stock
        fields = '__all__'

```
Finally, we need create a API View using your serializer and your filter. We create "viewsets.py" and type:
```
from rest_framework import viewsets
from .models import Stock
from .serializers import StockSerializer
from .filters import StockFilter


class StockViewSet(viewsets.ModelViewSet):
    queryset = Stock.objects.all()
    serializer_class = StockSerializer
    filterset_class = StockFilter
```
Now, we are registering this viewset in settings.router:
```
from rest_framework import routers
from stocks.viewsets import StockViewSet

router = routers.DefaultRouter()
router.register('stocks', StockViewSet)

urlpatterns = [
    ...
    path('api/v1/', include(router.urls)),
    ...
]
```
Maybe you need a good config for restframework.auth. Using JWT, Cookies, or whatever you want.

The API (is full CRUD + filters) is available in: http://localhost:8000/api/v1/stocks/

I recommend "drf-spectacular" for generate API documentation in swagger, or redoc format.

I hope this is useful for someone.

Greats. Ricardo.
Follow me for more content.
<a href="https://dev.to/makiolo">
  <img src="https://d2fltix0v2e0sb.cloudfront.net/dev-badge.svg" alt="Ricardo's DEV Community Profile" height="30" width="30">
</a>