Table Of Contents
Create model
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:
Create frontend
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">…</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">…</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:
We have search, pagination, edit form, create form and delete. All this, with minimal code and very customizable.
Create API
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.
Top comments (0)