article

Showing posts with label Python-Django. Show all posts
Showing posts with label Python-Django. Show all posts

Monday, March 10, 2025

Reactjs 18 Python Django CRUD (Create, Read, Update and Delete) with Pagination | Tanstack Query Tailwind CSS

Reactjs 18 Python Django CRUD (Create, Read, Update and Delete) with Pagination | Tanstack Query Tailwind CSS

react.dev
Create react project
Run the following command in your terminal: vite_dev/guide/
npm create vite@latest
tanstack/react-query
Hooks for fetching, caching and updating asynchronous data in React, Solid, Svelte and Vue

npm i @tanstack/react-query

react-query-devtools
npm i @tanstack/react-query-devtools

Tailwind CSS with Vite
tailwindcss_com/docs/guides/vite

Install npm i uuid

my-app\src\App.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//src\App.jsx
import { Route, Routes } from "react-router-dom" //npm i react-router-dom
import Home from "./pages/Home"
import Create from "./pages/Create"
import Read from "./pages/Read"
import EditCustomer from "./pages/EditCustomer"
 
function App() {
  return (
    <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/create/" element={<Create />} />
        <Route path="/read/:id" element={<Read />} />
        <Route path="/edit/:id" element={<EditCustomer />} />
    </Routes>
  )
}
  
export default App
my-app\src\main.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//src\main.jsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from '@tanstack/react-query-devtools' //npm i @tanstack/react-query-devtools npmjs com/package/@tanstack/react-query-devtools
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import { BrowserRouter } from "react-router-dom";
import App from './App.jsx'
  
// create a client
const queryClient = new QueryClient();
  
createRoot(document.getElementById("root")).render(
  <StrictMode>
    <BrowserRouter>
      <QueryClientProvider client={queryClient}>
        <App />
        <ReactQueryDevtools initialIsOpen={false} />
      </QueryClientProvider>
    </BrowserRouter>
  </StrictMode>,
);
Home Page : my-app\src\pages\Home.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//src\pages\Home.jsx
import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query tanstack com/query/latest/docs/framework/react/installation
import { getCustomerList } from "../api/customer";
import Pagination from "../components/Pagination";
import { useSearchParams } from "react-router-dom"
import CustomerList from "../pages/CustomerList";
 
export default function Home() {
    const [searchParams] = useSearchParams();
    const page = Number(searchParams.get("page"));
    //console.log(page)
  
    const currentPage = Number(page) || 1;
    //const currentPage = 1;
  
    const { isLoading, data, isError,  error } = useQuery({
        queryKey: ["customers", currentPage],
        queryFn: () => getCustomerList(currentPage)
    });
  
    //console.log(data); 
    if (isLoading) return "Loading...";
    if (isError) return `Error: ${error.message}`;
  
    const totalPages = Math.ceil(Number(data.totalpage) / Number(data.perpage));
    //console.log(totalPages);
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="flex items-center justify-between gap-1 mb-5 pl-10 pr-10">
            <h1 className="text-4xl font-bold">Reactjs 18 Python Django CRUD (Create, Read, Update and Delete) with Pagination | Tanstack Query Tailwind CSS</h1>
        </div>
        <div className="overflow-x-auto pt-10">
            <div className="mb-2 w-full text-right">
                <a
                href="/create"
                className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">
                Add New
                </a>
            </div>
            <CustomerList customerlist={data.customerlist} />
            <div className="flex items-center justify-between my-5">
                <Pagination totalPages={totalPages}/>
            </div>
        </div>
    </div>
  );
}
Customer List page : my-app\src\pages\CustomerList.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//my-app\src\pages\CustomerList.jsx
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; //npm i @tanstack/react-query
import { deleteCustomer } from "../api/customer";
 
const CustomerList = ({ customerlist }) => {
    const queryClient = useQueryClient();
 
    const deleteCustomerMutation = useMutation({
      mutationFn: deleteCustomer,
      onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: ['customers']});
      }
    });
  
    const handleDelete = (id) => {
      deleteCustomerMutation.mutate(id)
    }
 
  return (
    <>
        <table className="table table-zebra">
            <thead className="text-sm text-gray-700 uppercase bg-gray-50">
                <tr>
                    <th className="py-3 px-6">ID</th>
                    <th className="py-3 px-6">Name</th>
                    <th className="py-3 px-6">Email</th>
                    <th className="py-3 px-6 text-center">Actions</th>
                </tr>
            </thead>
            <tbody>
                {customerlist.map((item) => (
                    <tr key={item.id} className="bg-white border-b text-black">
                        <td className="py-3 px-6">
                        {item.id}
                        </td>
                        <td className="py-3 px-6">{item.name}</td>  
                        <td className="py-3 px-6">{item.email}</td>
                        <td className="flex justify-center gap-1 py-3">
                            <a className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
                                href={`/read/${item.id}`}>Read</a>
                               
                            <a className="focus:outline-none text-white bg-yellow-400 hover:bg-yellow-500 focus:ring-4 focus:ring-yellow-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:focus:ring-yellow-900"
                                href={`/edit/${item.id}/`}>
                                Edit
                            </a>
                            <button onClick={() => handleDelete(item.id)}   className="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900">
                                Delete</button>
                        </td>
                    </tr>
                ))}
            </tbody>
        </table>
    </>
  );
};
 
export default CustomerList;
Create page : my-app\src\pages\Create.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//my-app\src\pages\Create.jsx
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { createCustomer } from "../api/customer"
import UserForm from "../components/UserForm"
import {useNavigate} from 'react-router-dom'
 
const Create = () => {
  const queryClient = useQueryClient();
  
  const navigate = useNavigate()
 
  const createCustomerMutation = useMutation({
    mutationFn: createCustomer,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['customers']});
      console.log("success!");
      navigate('/')
    }
  });
  
  const handleAddPost = (customer) => {
    createCustomerMutation.mutate({
      ...customer
    })
  }
  
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="max-w-md mx-auto mt-5">
        <h1 className="text-2xl text-center mb-2">Add New Customer</h1>
        <UserForm onSubmit={handleAddPost} initialValue={{}} />
        </div>
    </div>
  )
}
  
export default Create
Read page : my-app\src\pages\Read.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//my-app\src\pages\Read.jsx
import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query
import { useNavigate, useParams } from "react-router-dom";
import { fetchCustomer } from "../api/customer";
  
const Read = () => {
    const navigate = useNavigate();
    const { id } = useParams();
    //console.log(id);
    const {
        isLoading,
        isError,
        data: customer,
        error,
    } = useQuery({
        queryKey: ["customers", id],
        queryFn: () => fetchCustomer(id),
    });
     
    if (isLoading) return "loading...";
    if (isError) return `Error: ${error.message}`;
  
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="overflow-x-auto py-10">
        <button className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
            onClick={() => navigate("/")}>back to list customer</button>
        <h1>{customer.getcustomer.name}</h1>
        <p>{customer.getcustomer.email}</p>
        </div>
    </div>
  )
}
  
export default Read
Read page : my-app\src\pages\EditCustomer.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//my-app\src\pages\EditCustomer.jsx
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useNavigate, useParams } from "react-router-dom";
import { fetchCustomer, updateCustomer } from "../api/customer";
import UserForm from "../components/UserForm"
  
const EditCustomer = () => {
    const queryClient = useQueryClient();
    const navigate = useNavigate();
    const { id } = useParams();
 
    const {
        isLoading,
        isError,
        data: customer,
        error,
    } = useQuery({
        queryKey: ["customers", id],
        queryFn: () => fetchCustomer(id),
    });
    //console.log(customer);
 
    const updateUserMutation = useMutation({
        mutationFn: updateCustomer,
        onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: ['customers']});
        navigate("/")
        }
    })
     
    if (isLoading) return "loading...";
    if (isError) return `Error: ${error.message}`;
     
    const handleSubmit = (updatedCustomer) => {
        updateUserMutation.mutate({id, ...updatedCustomer})
    }
     
  
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="overflow-x-auto py-10">
            <h1>{customer.getcustomer.name}</h1>
            <UserForm onSubmit={handleSubmit} initialValue={customer.getcustomer} />
        </div>
    </div>
  )
}
  
export default EditCustomer
Form : my-app\src\components\UserForm.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//src\components\UserForm.jsx
import { useState } from "react"
  
const UserForm = ({ onSubmit, initialValue }) => {
  const [user, setUser] = useState({
    name: initialValue.name || "",
    email: initialValue.email || ""
  });
  
  const handleChangeInput = (e) => {
    setUser({
      ...user,
      [e.target.name]: e.target.value
    })
  }
  
  const renderField = (label) => (
    <div>
      <label className="block text-sm font-medium text-gray-900">{label}</label>
      <input onChange={handleChangeInput} type="text" name={label.toLowerCase()} value={user[label.toLowerCase()]}
        className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500blue-500"
      />
    </div>
  );
  
  const handleSubmit = (e) => {
    e.preventDefault();
    onSubmit(user);
    setUser({
      name: "",
      email: ""
    })
  
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <div className="mb-5">{renderField('Name')}</div>
      <div className="mb-5">{renderField('Email')}</div>
      <button type="submit"
        className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
      >
        Submit</button>
    </form>
  )
}
  
export default UserForm
Api : my-app\src\api\customer.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//src\api\customer.jsx
export async function getCustomerList(page) {
  const response = await fetch(`http://localhost:8000/api/customers/?page=${page}`);
  //const response = await fetch("http://localhost:8000/api/customers/");
  const data = await response.json();
  //console.log(data);
  return {
    customerlist: data.results,  //customerlist: data,
    totalpage: data.count,
    perpage: 3
  }
}
  
export async function fetchCustomer(id) {
  const response = await fetch(`http://localhost:8000/api/customers/${id}/`);
  const data = await response.json();
  //console.log(data);
  return {
    getcustomer: data
  }
}
  
export async function createCustomer(newCustomer) {
  const response = await fetch("http://localhost:8000/api/customers/", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(newCustomer)
  });
  console.log(response);
  return response.json()
}
  
export async function updateCustomer(updatedCustomer) {
  const response = await fetch(`http://localhost:8000/api/customers/${updatedCustomer.id}/`, {
    method: "PUT",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(updatedCustomer)
  });
  console.log(response)
  return response.json()
}
  
export async function deleteCustomer(id) {
  const response = await fetch(`http://localhost:8000/api/customers/${id}/`, {
    method: "DELETE",
  });
  return response.json()
}
Pagination : my-app\src\components\Pagination.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//src\components\Pagination.jsx
import { HiChevronLeft, HiChevronRight } from "react-icons/hi"; //npm install react-icons --save npmjs com/package/react-icons
import clsx from "clsx"; //npm i clsx npmjs com/package/clsx
import { useSearchParams, useLocation } from "react-router-dom"
 
export const generatePagination = (currentPage, totalPages) => {
  if (totalPages <= 7) {
    return Array.from({ length: totalPages }, (_, i) => i + 1);
  }
    
  if (currentPage <= 3) {
    return [1, 2, 3, "...", totalPages - 1, totalPages];
  }
   
  if (currentPage >= totalPages - 2) {
    return [1, 2, 3, "...", totalPages - 2, totalPages - 1, totalPages];
  }
   
  return [
    1,
    "...",
    currentPage - 1,
    currentPage,
    currentPage + 1,
    "...",
    totalPages,
  ];
};
 
const Paginationnumber = ({ totalPages }) => {
  let location = useLocation();
  console.log(location);
  const { hash, pathname, search } = location;
  //console.log(pathname);
 
  const [searchParams] = useSearchParams();
  const currentPage = Number(searchParams.get("page")) || 1;
  //console.log(currentPage); 2
 
  const allPages = generatePagination(currentPage, totalPages);
 
  const createPageURL = (pageNumber) => {
      const params = searchParams;
      params.set("page", pageNumber.toString());
      return `${pathname}?${params.toString()}`;
  };
 
  const PaginationNumber = ({
      page,
      href,
      position,
      isActive,
  }) => {
      const className = clsx(
          "flex h-10 w-10 items-center justify-center text-sm border",
          {
              "rounded-l-sm": position === "first" || position === "single",
              "rounded-r-sm": position === "last" || position === "single",
              "z-10 bg-blue-700 border-blue-500 text-white bg-blue-700": isActive,
              "hover:bg-blue-700": !isActive && position !== "middle",
              "text-gray-300 pointer-events-none": position === "middle",
          }
      );
 
      return isActive && position === "middle" ? (
          <div className={className}>{page}</div>
      ) : (
          <a href={href} className={className}>
              {page}
          </a>
      );
  };
 
  const PaginationArrow = ({ href, direction, isDisabled }) => {
      const className = clsx(
          "flex h-10 w-10 items-center justify-center text-sm border",
          {
              "pointer-events-none text-blue-300": isDisabled,
              "hover:bg-blue-700": !isDisabled,
              "mr-2": direction === "left",
              "ml-2": direction === "right",
          }
      );
 
      const icon =
        direction === "left" ? (
          <HiChevronLeft size={20} />
        ) : (
          <HiChevronRight size={20} />
        );
 
    return isDisabled ? (
      <div className={className}>{icon}</div>
    ) : (
      <a href={href} className={className}>
        {icon}
      </a>
    );
  };
 
  return (
    <div className="inline-flex">
      <PaginationArrow
        direction="left"
        href={createPageURL(currentPage - 1)}
        isDisabled={currentPage <= 1}
      />
      <div className="flex -space-x-px">
        {allPages.map((page, index) => {
          let position;
 
          if (index === 0) position = "first";
          if (index === allPages.length - 1) position = "last";
          if (allPages.length === 1) position = "single";
          if (page === "...") position = "middle";
 
          return (
            <PaginationNumber
              key={index}
              href={createPageURL(page)}
              page={page}
              position={position}
              isActive={currentPage === page}
            />
          );
        })}
      </div>
      <PaginationArrow
        direction="right"
        href={createPageURL(currentPage + 1)}
        isDisabled={currentPage >= totalPages}
      />
    </div>
  );
};
 
export default Paginationnumber;
Run
C:\react-js\my-app> npm run dev

Python Django

Step 1 — Setting Up the Backend

Now install Pipenv using pip:
ednalan@Cairocoders django-nextjs % pip install pipenv

And activate a new virtual environment:
pipenv shell
ednalan@Cairocoders django-nextjs % pipenv shell

Install Django using Pipenv:
pipenv install django
ednalan@Cairocoders django-nextjs % pipenv install django

Then create a new project called backend:
django-admin startproject backend
ednalan@Cairocoders django-nextjs % django-admin startproject backend

navigate into the newly created backend directory:
cd backend
ednalan@Cairocoders backend %

Start a new application called myapp:
ednalan@Cairocoders backend % python manage.py startapp myapp

Run migrations:
ednalan@Cairocoders backend % python manage.py migrate

Navigate to http://localhost:8000 in your web browser:

Registering the myapp Application
Open the backend/settings.py file in your code editor and add myapp to the INSTALLED_APPS:

1
2
3
4
5
6
7
8
9
10
//backend/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp',
]
save your changes.

Defining the Customer Model
Open the myapp/models.py file in your code editor and add the following lines of code:

1
2
3
4
5
6
7
8
9
10
//myapp/models.py
from django.db import models
 
# Create your models here.
class Customer(models.Model):
    name = models.CharField(max_length=124)
    email = models.CharField(max_length=125)
 
    def _str_(self):
        return self.name
Create a migration file
python manage.py makemigrations myapp
And apply the changes to the database: python manage.py migrate myapp Open the myapp/admin.py file with your code editor and add the following lines of code:
1
2
3
4
5
6
7
8
9
10
//myapp/admin.py
from django.contrib import admin
from .models import Customer
 
class CustomerAdmin(admin.ModelAdmin):
    list_display = ('name', 'email')
 
# Register your models here.
 
admin.site.register(Customer, CustomerAdmin)
create a “superuser” account to access the admin interface. Run the following command in your terminal:
python manage.py createsuperuser
Start the server once again:
python manage.py runserver

Navigate to http://localhost:8000/admin in your web browser. And log in with the username and password that was created You can create, edit, and, delete items using this interface:

Step 2 — Setting Up the APIs create an API using the Django REST framework.
Install the djangorestframework and django-cors-headers using Pipenv:
pipenv install djangorestframework django-cors-headers

need to add rest_framework and corsheaders to the list of installed applications. Open the backend/settings.py file in your code editor and update the INSTALLED_APPS and MIDDLEWARE sections:

INSTALLED_APPS = [
'corsheaders',
'rest_framework',
]

MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
]

add these lines of code to the bottom of the backend/settings.py file:
CORS_ORIGIN_WHITELIST = [
'http://localhost:3000'
]

Creating serializers
serializers to convert model instances to JSON so that the frontend can work with the received data.
myapp/serializers.py file with your code editor. Open the serializers.py file and update it with the following lines of code:

1
2
3
4
5
6
7
8
//myapp/serializers.py
from rest_framework import serializers
from .models import Customer
 
class CustomerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Customer
        fields = ('id', 'name', 'email')
This code specifies the model to work with and the fields to be converted to JSON.
Creating View
myapp/views.py
1
2
3
4
5
6
7
8
9
10
11
//myapp/views.py
from django.shortcuts import render
from rest_framework import viewsets
from .serializers import CustomerSerializer
from .models import Customer
 
# Create your views here.
 
class CustomerView(viewsets.ModelViewSet):
    serializer_class = CustomerSerializer
    queryset = Customer.objects.all()
Open the backend/urls.py file with your code editor and replace the contents with the following lines of code:
1
2
3
4
5
6
7
8
9
10
11
12
13
//backend/urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from myapp import views
 
router = routers.DefaultRouter()
router.register(r'customers', views.CustomerView, 'customer')
 
urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include(router.urls)),
]
restart the server:
python manage.py runserver
Navigate to http://localhost:8000/api/customers http://localhost:8000/api/customers/1 in your web browser:

Pagination
django-rest-framework
PageNumberPagination
Request:
GET https://api.example.org/accounts/?page=4
Setup
To enable the PageNumberPagination style globally, use the following configuration, and set the PAGE_SIZE as desired:
backend/settings.py
1
2
3
4
REST_FRAMEWORK = { #https://www.django-rest-framework.org/api-guide/pagination/#pagenumberpagination
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 3
}
http://localhost:8000/api/customers/?page=1

Wednesday, March 5, 2025

Nextjs 15 Python Django CRUD (Create, Read, Update and Delete) with Pagination | Tanstack Query Tailwind CSS

Nextjs 15 Python Django CRUD (Create, Read, Update and Delete) with Pagination | Tanstack Query Tailwind CSS

Install nextjs npx create-next-app@latest nextjs_org/docs/getting-started/installation

Install react-query

npm i @tanstack/react-query
tanstack com/query/latest/docs/framework/react/installation
app\page.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//app\page.tsx
"use client";
 
import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query tanstack com/query/latest/docs/framework/react/installation
import CustomerList from "./components/CustomerList";
import { getCustomerList } from "./api/customer";
import Paginationnumber from "./components/Paginationnumber";
import LoadingSpinner from "./components/LoadingSpinner";
import { useSearchParams } from 'next/navigation'
import Link from "next/link";
 
export default function Home() {
    const searchParams = useSearchParams()
    const page = searchParams.get('page')
    //console.log(page)
 
    const currentPage = Number(page) || 1;
 
    const { isLoading, data, isError, isFetching,  error } = useQuery({
        queryKey: ["customers", currentPage],
        queryFn: () => getCustomerList(currentPage)
    });
 
    console.log(data); 
    if (isLoading) return "Loading...";
    if (isError) return `Error: ${error.message}`;
 
    const totalPages = Math.ceil(Number(data.totalpage) / Number(data.perpage));
    //console.log(totalPages);
 
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="flex items-center justify-between gap-1 mb-5 pl-10 pr-10">
            <h1 className="text-4xl font-bold">Nextjs 15 Python Django CRUD (Create, Read, Update and Delete) with Pagination | Tanstack Query Tailwind CSS</h1>
        </div>
        <div className="overflow-x-auto pt-10">
            <div className="mb-2 w-full text-right">
                <Link
                href="/customer/create"
                className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">
                Add New
                </Link>
            </div>
            <CustomerList customerlist={data.customerlist} />
            <div className="flex items-center justify-between my-5">
                <Paginationnumber totalPages={totalPages} />
                {isFetching ? <LoadingSpinner /> : null}
            </div>
        </div>
    </div>
  );
}
app\layout.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//app\layout.tsx
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import QueryProvider from "./components/QueryProvider";
 
const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});
 
const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});
 
export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};
 
export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <QueryProvider>{children}</QueryProvider>
      </body>
    </html>
  );
}
app\components\QueryProvider.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//app\components\QueryProvider.tsx
"use client";
 
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
import { useState } from "react";
 
interface Props {
  children: React.ReactNode;
}
 
export default function QueryProvider({ children }: Props) {
  const [queryClient] = useState(() => new QueryClient());
  return (
    <QueryClientProvider client={queryClient}> {children} </QueryClientProvider>
  );
}
Table List : app\components\CustomerList.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//app\components\CustomerList.tsx
import Link from "next/link";
import { useMutation, useQueryClient } from "@tanstack/react-query"; //npm i @tanstack/react-query
import { deleteCustomer } from "../api/customer";
 
const CustomerList = ({ customerlist }) => {
    const queryClient = useQueryClient();
 
    const deleteCustomerMutation = useMutation({
      mutationFn: deleteCustomer,
      onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: ['customers']});
      }
    });
  
    const handleDelete = (id) => {
        deleteCustomerMutation.mutate(id)
    }
  return (
    <>
        <table className="table table-zebra">
            <thead className="text-sm text-gray-700 uppercase bg-gray-50">
                <tr>
                    <th className="py-3 px-6">ID</th>
                    <th className="py-3 px-6">Name</th>
                    <th className="py-3 px-6">Email</th>
                    <th className="py-3 px-6 text-center">Actions</th>
                </tr>
            </thead>
            <tbody>
                {customerlist.map((item) => (
                    <tr key={item.id} className="bg-white border-b text-black">
                        <td className="py-3 px-6">
                        {item.id}
                        </td>
                        <td className="py-3 px-6">{item.name}</td>  
                        <td className="py-3 px-6">{item.email}</td>
                        <td className="flex justify-center gap-1 py-3">
                            <Link className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
                                href={`/customer/${item.id}`}>Read</Link>
                              
                            <Link className="focus:outline-none text-white bg-yellow-400 hover:bg-yellow-500 focus:ring-4 focus:ring-yellow-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:focus:ring-yellow-900"
                                href={`/customer/edit/${item.id}/`}>
                                Edit
                            </Link>
                            <button onClick={() => handleDelete(item.id)}   className="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900">
                                Delete</button>
                        </td>
                    </tr>
                ))}
            </tbody>
        </table>
    </>
  )
}
  
export default CustomerList
Loader : app\components\LoadingSpinner.tsx
1
2
3
4
5
6
//app\components\LoadingSpinner.tsx
export default function LoadingSpinner() {
    return (
        <h1>Loading...</h1>
    )
}
Create page : app\customer\create\page.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//app\customer\create\page.tsx
"use client";
 
import { useMutation, useQueryClient } from "@tanstack/react-query"
import CustomertForm from "../../components/CustomertForm"
import { createCustomer } from "../../api/customer"
import { useRouter } from 'next/navigation'
 
const AddCustomer= () => {
  const queryClient = useQueryClient();
  const router = useRouter();
 
  const createUserMutation = useMutation({
    mutationFn: createCustomer,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['customers']});
      //console.log("success!")
      router.push('/')
    }
  });
  
  const handleAddPost = (customer) => {
    //console.log(customer)
    createUserMutation.mutate({
      ...customer
    })
  }
    
  return (
    <div className="max-w-md mx-auto mt-5 mb-5">
      <h1 className="text-2xl text-center mb-2">Add New Customer</h1>
      <CustomertForm onSubmit={handleAddPost} initialValue={{}} />
    </div>
  )
}
   
export default AddCustomer
Form : app\components\CustomertForm.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//app\components\CustomertForm.tsx
"use client";
 
import { useState } from "react"
 
const CustomertForm = ({ onSubmit, initialValue }) => {
  const [customer, setCustomer] = useState({
    name: initialValue.name || "",
    email: initialValue.email || "",
    contactnumber: initialValue.contactnumber || ""
  });
 
  //console.log(customer);
  const handleChangeInput = (e) => {
    setCustomer({
      ...customer,
      [e.target.name]: e.target.value
    })
  }
 
  const renderField = (label) => (
    <div>
      <label className="block text-sm font-medium text-gray-900">{label}</label>
      <input onChange={handleChangeInput} type="text" name={label.toLowerCase()} value={customer[label.toLowerCase()]}
        className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500blue-500"
      />
    </div>
  );
 
  const handleSubmit = (e) => {
    e.preventDefault();
    onSubmit(customer);
    setCustomer({
      name: "",
      email: "",
      contactnumber: "",
    })
   
  }
   
  return (
    <form onSubmit={handleSubmit}>
      <div className="mb-5">{renderField('Name')}</div>
      <div className="mb-5">{renderField('Email')}</div>
      <button type="submit"
        className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
      >
        Submit</button>
    </form>
  )
}
   
export default CustomertForm
Read : app\customer\[id]\page.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//app\customer\[id]\page.tsx
"use client";
 
import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query
import { useParams } from 'next/navigation'
import { fetchCustomer } from "../../api/customer";
 
const ViewCustomer = () => {
  const {id}=useParams();
  //console.log(id);
 
  const {
    isLoading,
    isError,
    data: customer,
    error,
  } = useQuery({
    queryKey: ["customer", id],
    queryFn: () => fetchCustomer(id),
  });
 
  if (isLoading) return "loading...";
  if (isError) return `Error: ${error.message}`;
  console.log(customer);
 
  return (
    <div className="min-h-screen flex items-center justify-center bg-slate-100">
      <div className="bg-white rounded-sm shadow p-8 text-black">
      <div className="max-w-2xl mx-auto mt-5">
        <h1 className="text-2xl text-center mb-2">View Customer</h1>
        <h2>ID : {customer.getcustomer.id}</h2>
        <h2>Name : {customer.getcustomer.name}</h2>
        <h2>Email : {customer.getcustomer.email}</h2>
      </div>
      </div>
    </div>
  )
}
   
export default ViewCustomer
Edit : app\customer\edit\[id]\page.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//app\customer\edit\[id]\page.tsx
"use client";
  
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useParams, useRouter } from 'next/navigation'
import { fetchCustomer, updateCustomer } from "../../../api/customer";
import CustomertForm from "../../../components/CustomertForm"
  
const EditCustomer = () => {
  const {id}=useParams();
  //console.log(id);
  
  const queryClient = useQueryClient();
  const router = useRouter();
  
  const {
    isLoading,
    isError,
    data: customer,
    error,
  } = useQuery({
    queryKey: ["customers", id],
    queryFn: () => fetchCustomer(id),
  });
  
  console.log(customer);
 
  const updateCustomerMutation = useMutation({
    mutationFn: updateCustomer,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['customers']});
      console.log("success");
      router.push('/')
    }
  })
  
  if (isLoading) return "loading...";
  if (isError) return `Error: ${error.message}`;
  
  const handleSubmit = (updatedCustomer) => {
    updateCustomerMutation.mutate({id, ...updatedCustomer})
  }
  
  return (
    <div className="max-w-md mx-auto mt-5">
        <div className="flex items-center justify-between gap-1 mb-5">
            <h1 className="text-4xl font-bold">{customer.getcustomer.name}</h1>
        </div>
        <CustomertForm onSubmit={handleSubmit} initialValue={customer.getcustomer} />
    </div>
  )
}
   
export default EditCustomer
Pagination : app\components\Paginationnumber.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//app\components\Paginationnumber.tsx
"use client";
  
import Link from "next/link";
import { HiChevronLeft, HiChevronRight } from "react-icons/hi"; //npm install react-icons --save npmjs com/package/react-icons
import { usePathname, useSearchParams } from "next/navigation";
import clsx from "clsx"; //npm i clsx npmjs com/package/clsx
  
export const generatePagination = (currentPage: number, totalPages: number) => {
  if (totalPages <= 7) {
    return Array.from({ length: totalPages }, (_, i) => i + 1);
  }
   
  if (currentPage <= 3) {
    return [1, 2, 3, "...", totalPages - 1, totalPages];
  }
  
  if (currentPage >= totalPages - 2) {
    return [1, 2, 3, "...", totalPages - 2, totalPages - 1, totalPages];
  }
  
  return [
    1,
    "...",
    currentPage - 1,
    currentPage,
    currentPage + 1,
    "...",
    totalPages,
  ];
};
 
const Paginationnumber = ({ totalPages }: { totalPages: number }) => {
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const currentPage = Number(searchParams.get("page")) || 1;
  
  const createPageURL = (pageNumber: string | number) => {
    const params = new URLSearchParams(searchParams);
    params.set("page", pageNumber.toString());
    return `${pathname}?${params.toString()}`;
  };
  
  const allPages = generatePagination(currentPage, totalPages);
  
  const PaginationNumber = ({
    page,
    href,
    position,
    isActive,
  }: {
    page: number | string;
    href: string;
    position?: "first" | "last" | "middle" | "single";
    isActive: boolean;
  }) => {
    const className = clsx(
      "flex h-10 w-10 items-center justify-center text-sm border",
      {
        "rounded-l-sm": position === "first" || position === "single",
        "rounded-r-sm": position === "last" || position === "single",
        "z-10 bg-blue-700 border-blue-500 text-white bg-blue-700": isActive,
        "hover:bg-blue-700": !isActive && position !== "middle",
        "text-gray-300 pointer-events-none": position === "middle",
      }
    );
  
    return isActive && position === "middle" ? (
      <div className={className}>{page}</div>
    ) : (
      <Link href={href} className={className}>
        {page}
      </Link>
    );
  };
    
  const PaginationArrow = ({
    href,
    direction,
    isDisabled,
  }: {
    href: string;
    direction: "left" | "right";
    isDisabled?: boolean;
  }) => {
    const className = clsx(
      "flex h-10 w-10 items-center justify-center text-sm border",
      {
        "pointer-events-none text-blue-300": isDisabled,
        "hover:bg-blue-700": !isDisabled,
        "mr-2": direction === "left",
        "ml-2": direction === "right",
      }
    );
  
    const icon =
      direction === "left" ? (
        <HiChevronLeft size={20} />
      ) : (
        <HiChevronRight size={20} />
      );
  
    return isDisabled ? (
      <div className={className}>{icon}</div>
    ) : (
      <Link href={href} className={className}>
        {icon}
      </Link>
    );
  };
  
  return (
    <div className="inline-flex">
      <PaginationArrow
        direction="left"
        href={createPageURL(currentPage - 1)}
        isDisabled={currentPage <= 1}
      />
      <div className="flex -space-x-px">
        {allPages.map((page, index) => {
          let position: "first" | "last" | "single" | "middle" | undefined;
  
          if (index === 0) position = "first";
          if (index === allPages.length - 1) position = "last";
          if (allPages.length === 1) position = "single";
          if (page === "...") position = "middle";
  
          return (
            <PaginationNumber
              key={index}
              href={createPageURL(page)}
              page={page}
              position={position}
              isActive={currentPage === page}
            />
          );
        })}
      </div>
      <PaginationArrow
        direction="right"
        href={createPageURL(currentPage + 1)}
        isDisabled={currentPage >= totalPages}
      />
    </div>
  );
};
  
export default Paginationnumber;
Route API : app\api\customer.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//app\api\customer.tsx
export async function getCustomerList(page) {
  const response = await fetch(`http://localhost:8000/api/customers/?page=${page}`);
  //const response = await fetch("http://localhost:8000/api/customers/");
  const data = await response.json();
  //console.log(data);
  return {
    customerlist: data.results,  //customerlist: data,
    totalpage: data.count,
    perpage: 3
  }
}
 
export async function fetchCustomer(id) {
  const response = await fetch(`http://localhost:8000/api/customers/${id}/`);
  const data = await response.json();
  //console.log(data);
  return {
    getcustomer: data
  }
}
 
export async function createCustomer(newCustomer) {
  const response = await fetch("http://localhost:8000/api/customers/", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(newCustomer)
  });
  console.log(response);
  return response.json()
}
 
export async function updateCustomer(updatedCustomer) {
  const response = await fetch(`http://localhost:8000/api/customers/${updatedCustomer.id}/`, {
    method: "PUT",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(updatedCustomer)
  });
  console.log(response)
  return response.json()
}
 
export async function deleteCustomer(id) {
  const response = await fetch(`http://localhost:8000/api/customers/${id}/`, {
    method: "DELETE",
  });
  return response.json()
}
run C:\nextjs>npm run dev

Step 1 — Setting Up the Backend

Now install Pipenv using pip:
ednalan@Cairocoders django-nextjs % pip install pipenv

And activate a new virtual environment:
pipenv shell
ednalan@Cairocoders django-nextjs % pipenv shell

Install Django using Pipenv:
pipenv install django
ednalan@Cairocoders django-nextjs % pipenv install django

Then create a new project called backend:
django-admin startproject backend
ednalan@Cairocoders django-nextjs % django-admin startproject backend

navigate into the newly created backend directory:
cd backend
ednalan@Cairocoders backend %

Start a new application called myapp:
ednalan@Cairocoders backend % python manage.py startapp myapp

Run migrations:
ednalan@Cairocoders backend % python manage.py migrate

Navigate to http://localhost:8000 in your web browser:

Registering the myapp Application
Open the backend/settings.py file in your code editor and add myapp to the INSTALLED_APPS:

1
2
3
4
5
6
7
8
9
10
//backend/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp',
]
save your changes.

Defining the Customer Model
Open the myapp/models.py file in your code editor and add the following lines of code:

1
2
3
4
5
6
7
8
9
10
//myapp/models.py
from django.db import models
 
# Create your models here.
class Customer(models.Model):
    name = models.CharField(max_length=124)
    email = models.CharField(max_length=125)
 
    def _str_(self):
        return self.name
Create a migration file
python manage.py makemigrations myapp
And apply the changes to the database: python manage.py migrate myapp Open the myapp/admin.py file with your code editor and add the following lines of code:
1
2
3
4
5
6
7
8
9
10
//myapp/admin.py
from django.contrib import admin
from .models import Customer
 
class CustomerAdmin(admin.ModelAdmin):
    list_display = ('name', 'email')
 
# Register your models here.
 
admin.site.register(Customer, CustomerAdmin)
create a “superuser” account to access the admin interface. Run the following command in your terminal:
python manage.py createsuperuser
Start the server once again:
python manage.py runserver

Navigate to http://localhost:8000/admin in your web browser. And log in with the username and password that was created You can create, edit, and, delete items using this interface:

Step 2 — Setting Up the APIs create an API using the Django REST framework.
Install the djangorestframework and django-cors-headers using Pipenv:
pipenv install djangorestframework django-cors-headers

need to add rest_framework and corsheaders to the list of installed applications. Open the backend/settings.py file in your code editor and update the INSTALLED_APPS and MIDDLEWARE sections:

INSTALLED_APPS = [
'corsheaders',
'rest_framework',
]

MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
]

add these lines of code to the bottom of the backend/settings.py file:
CORS_ORIGIN_WHITELIST = [
'http://localhost:3000'
]

Creating serializers
serializers to convert model instances to JSON so that the frontend can work with the received data.
myapp/serializers.py file with your code editor. Open the serializers.py file and update it with the following lines of code:

1
2
3
4
5
6
7
8
//myapp/serializers.py
from rest_framework import serializers
from .models import Customer
 
class CustomerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Customer
        fields = ('id', 'name', 'email')
This code specifies the model to work with and the fields to be converted to JSON.
Creating View
myapp/views.py
1
2
3
4
5
6
7
8
9
10
11
//myapp/views.py
from django.shortcuts import render
from rest_framework import viewsets
from .serializers import CustomerSerializer
from .models import Customer
 
# Create your views here.
 
class CustomerView(viewsets.ModelViewSet):
    serializer_class = CustomerSerializer
    queryset = Customer.objects.all()
Open the backend/urls.py file with your code editor and replace the contents with the following lines of code:
1
2
3
4
5
6
7
8
9
10
11
12
13
//backend/urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from myapp import views
 
router = routers.DefaultRouter()
router.register(r'customers', views.CustomerView, 'customer')
 
urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include(router.urls)),
]
restart the server:
python manage.py runserver
Navigate to http://localhost:8000/api/customers http://localhost:8000/api/customers/1 in your web browser:

Pagination
django-rest-framework
PageNumberPagination
Request:
GET https://api.example.org/accounts/?page=4
Setup
To enable the PageNumberPagination style globally, use the following configuration, and set the PAGE_SIZE as desired:
backend/settings.py
1
2
3
4
REST_FRAMEWORK = { #https://www.django-rest-framework.org/api-guide/pagination/#pagenumberpagination
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 3
}
http://localhost:8000/api/customers/?page=1

Thursday, June 30, 2022

Python Django Simple Blog with Summernote WYSIWYG Editor

Python Django Simple Blog with Summernote WYSIWYG Editor

Install django-summernote.

pip install django-summernote

django_summernote to INSTALLED_APP in settings.py.

INSTALLED_APPS += ('django_summernote', ) 

django_summernote.urls to urls.py.

from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [ 
    path('admin/', admin.site.urls), 
    path('summernote/', include('django_summernote.urls')),
]

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
 
Media settings
X_FRAME_OPTIONS = 'SAMEORIGIN'
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')


To explore more configuration read the official documentation of the package, https://github.com/summernote/django-summernote


blogsite/settings.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//blogsite/settings.py
     
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp', #add myapp
]
 
INSTALLED_APPS += ('django_summernote', )
 
X_FRAME_OPTIONS = 'SAMEORIGIN'
myapp/views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//myapp/views.py
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.contrib import messages
from myapp.models import Category, Post
 
category_list = Category.objects.exclude(status = 2).all()
context = {
    'page_title' : 'Simple Blog Site',
    'category_list' : category_list,
    'category_list_limited' : category_list[:3]
}
# Create your views here.
#Logout
def logoutuser(request):
    logout(request)
    return redirect('/')
 
def home(request):
    context['page_title'] = 'Home'
    posts = Post.objects.filter(status = 1).all()
    context['posts'] = posts
    return render(request, 'home.html',context)
 
def view_post(request,pk=None):
    context['page_title'] = ""
    if pk is None:
        messages.error(request,"Unabale to view Post")
        return redirect('home-page')
    else:
        post = Post.objects.filter(id = pk).first()
        context['page_title'] = post.title
        context['post'] = post
    return render(request, 'view_post.html',context)
 
def post_by_category(request,pk=None):
    if pk is None:
        messages.error(request,"Unabale to view Post")
        return redirect('home-page')
    else:
        category = Category.objects.filter(id=pk).first()
        context['page_title'] = category.name
        context['category'] = category
        posts = Post.objects.filter(category = category).all()
        context['posts'] = posts
    return render(request, 'by_categories.html',context)
 
def categories(request):
    categories = Category.objects.filter(status = 1).all()
    context['page_title'] = "Category Management"
    context['categories'] = categories
    return render(request, 'categories.html',context)
blogsite/urls.py
1
2
3
4
5
6
7
8
9
10
11
12
//blogsite/urls.py
from django.contrib import admin
from django.urls import include, path
   
from django.conf import settings
from django.conf.urls.static import static
   
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('myapp.urls')),
    path('summernote/', include('django_summernote.urls')),
] + static(settings.MEDIA_URL, document_root = settings.MEDIA_ROOT)
myspp/urls.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//myspp/urls.py
from . import views
from django.contrib import admin
from django.urls import path, re_path
from django.contrib.auth import views as auth_views
from django.views.generic.base import RedirectView
 
urlpatterns = [
    path('redirect-admin', RedirectView.as_view(url="/admin"),name="redirect-admin"),
    path('', views.home, name="home-page"),
    path('logout',views.logoutuser,name='logout'),
    path(r'view_post/<int:pk>',views.view_post,name='view-post'),
    path(r'<int:pk>',views.post_by_category,name='category-post'),
    path('categories',views.categories,name='category-page'),
]
Make Migrations
Run the commands below to make migrations:
python manage.py makemigrations
python manage.py migrate
C:\django\blogsite>python manage.py makemigrations
C:\django\blogsite>python manage.py migrate
myapp/models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//myapp/models.py
from django.db import models
from unicodedata import category
from django.db import models
from django.contrib.auth.models import User
 
from django.utils import timezone
 
class Category(models.Model):
    name = models.CharField(max_length=250)
    description = models.TextField(blank=True, null=True)
    status = models.IntegerField(default = 1)
    date_added = models.DateTimeField(default=timezone.now)
    date_updated = models.DateTimeField(auto_now=True)
 
    def __str__(self):
        return self.name
 
class Post(models.Model):
    category=models.ForeignKey(Category,on_delete=models.CASCADE)
    title = models.TextField()
    author = models.ForeignKey(User,on_delete=models.CASCADE)
    blog_post = models.TextField()
    banner = models.ImageField(blank=True, null = True, upload_to= 'images/')
    status = models.IntegerField(default = 0)
    date_added = models.DateTimeField(default=timezone.now)
    date_updated = models.DateTimeField(auto_now=True)
 
    def __str__(self):
        return self.title + " - " + self.category.name
Bootstrap 5
https://getbootstrap.com/docs/5.0/getting-started/introduction/
https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css
myapp/templates/home.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//myapp/templates/home.html
{% extends "base.html" %}
 
{% block pageContent %}
<section class="text-center">
    <h4 class="mb-5"><strong>All Posts</strong></h4>
    <div class="row">
        {% for post in posts %}
        <div class="col-lg-4 col-md-6 mb-4">
            <div class="card shadow border">
                <div class="bg-image hover-overlay ripple" data-mdb-ripple-color="light">
                    <img src="{% if post.banner %}{{ post.banner.url }}{% else %}{{ MEDIA_URL}}/media/default/python-django.png{% endif %}" class="img-fluid post-banner bg-gradient bg-dark" />
                    <a href="#!">
                        <div class="mask" style="background-color: rgba(251, 251, 251, 0.15);"></div>
                    </a>
                </div>
                <div class="card-body">
                    <h5 class="card-title">{{ post.title }}</h5>
                    <div class="card-text truncate-3">{{ post.blog_post|safe }} </div>
                    <a href="{% url 'view-post' post.id %}" class="btn btn-primary">Read</a>
                </div>
            </div>
        </div>
        {% endfor %}
 
 
    </div>
    {% if not posts %}
    <center>No Bogs has been posted yet</center>
    {% endif %}
</section>
</div> 
{% endblock pageContent %}
myapp/templates/base.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//myapp/templates/base.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    {% if page_title %}
    <title>{{ page_title }} | Blog Site</title>
    {% else %}
    <title>Blog Site</title>
    {% endif %}
    <link rel="stylesheet" href="{% static 'blogApp/assets/bootstrap/css/bootstrap.min.css' %}">
</head>
<body>
    {% block TopNavigation %} {% include "TopNavigation.html" %} {% endblock TopNavigation %}
    <main class="my-5">
        <div class="container" style="padding-top: 50px;">
            {% block pageContent %} {% endblock pageContent %}
        </div>
    </main>
    {% block ScriptBlock %} {% endblock ScriptBlock %}
    <footer class="bg-light text-lg-start">
        <div class="text-center p-3" style="background-color: rgba(0, 0, 0, 0.2);">
            © {% now 'Y' %} Copyright:
            <a class="text-dark" href="#" target="_blank">Cairocoders</a>
        </div>
    </footer>
</body>
</html>
myapp/templates/TopNavigation.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//myapp/templates/TopNavigation.html
{% load static %}
<header>
    <nav class="navbar navbar-expand-lg navbar-light bg-white fixed-top">
        <div class="container">
            <a class="navbar-brand" target="_blank" href="#">Cairocoders</a>
            <button class="navbar-toggler" type="button" data-mdb-toggle="collapse" data-mdb-target="#navbarExample01" aria-controls="navbarExample01" aria-expanded="false" aria-label="Toggle navigation">
            <i class="fas fa-bars"></i>
            </button>
            <div class="collapse navbar-collapse" id="navbarExample01">
                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                    <li class="nav-item active">
                        <a class="nav-link" aria-current="page" href="{% url 'home-page' %}">Home</a>
                    </li>
                    {% for category in category_list_limited %}
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'category-post' category.pk %}">{{ category.name }}</a>
                    </li>
                    {% endfor %}
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'category-page' %}">All Categories</a>
                    </li>
                </ul>
 
                <ul class="navbar-nav d-flex flex-row">
                    {% if user.id %}
                    <li class="nav-item me-3 me-lg-0">
                        Hello, {{ user.first_name }} {{user.last_name}}
                    </li>
                    <li class="nav-item me-3 me-lg-0">
                        <a class="nav-link" href="{% url 'logout' %}">Logout</a>
                    </li>
                    {% endif %}
                </ul>
            </div>
        </div>
    </nav>
</header>
myapp/templates/view_post.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//myapp/templates/view_post.html
{% extends "base.html" %}
 
{% block pageContent %}
<section class="text-center">
    <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
        <div class="row justify-content-center">
            <div class=" col-lg-12 col-md-12 col-sm-12 col-xs-12 card card-default rounded-0 shadow">
                <div class="card-body">
                    <center>
                        <img src="{% if post.banner %}{{ post.banner.url }}{% else %}{{ MEDIA_URL}}/media/default/python-django.png{% endif %}" alt="" class="img-fluid bg-gradient" id="view-post-banner">
                    </center>
                    <h4 class="fw-bolder mt-4 text-start">{{ post.title }}</h4>
                    <hr>
                    <div class="lh-1 text-start">
                        <span class="me-5"><small>Author: <b>{{ post.author }}</b></small></span>
                        <span class="me-5"><small>Category: <b>{{ post.category }}</b></small></span>
                        <span><small><i class="fa fa-calendar-day"></i> Published: <b>{{ post.date_added|date:"F d, Y h:i A" }}</b></small></span>
 
                    </div>
                    <div class="clear-fix py-3"></div>
                    <div>{{ post.blog_post|safe }}</div>
                </div>
            </div>
        </div>
    </div>
</section>
</div>
{% endblock pageContent %}
myapp/templates/by_categories.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//myapp/templates/by_categories.html
{% extends "base.html" %}
 
{% block pageContent %}
<section class="text-center">
    <h4 class="mb-5"><strong>'{{ category }}' Posts</strong></h4>
 
    <div class="row">
        {% for post in posts %}
        <div class="col-lg-4 col-md-6 mb-4">
            <div class="card shadow border">
                <div class="bg-image hover-overlay ripple" data-mdb-ripple-color="light">
                    <img src="{% if post.banner %}{{ post.banner.url }}{% else %}{{ MEDIA_URL}}/media/default/python-django.png{% endif %}" class="img-fluid post-banner bg-gradient bg-dark" />
                    <a href="#!">
                        <div class="mask" style="background-color: rgba(251, 251, 251, 0.15);"></div>
                    </a>
                </div>
                <div class="card-body">
                    <h5 class="card-title">{{ post.title }}</h5>
                    <div class="card-text truncate-3">{{ post.blog_post|safe }} </div>
                    <a href="{% url 'view-post' post.id %}" class="btn btn-primary">Read</a>
                </div>
            </div>
        </div>
        {% endfor %}
 
 
    </div>
    {% if not posts %}
    <center>No Bogs has been posted yet</center>
    {% endif %}
 
</section>
</div>
{% endblock pageContent %}
myapp/templates/categories.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//myapp/templates/categories.html
{% extends 'base.html' %} {% block pageContent %}
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
    <div class="card card-default rounded-0 shadow ">
        <div class="card-header">
            <div class="d-flex w-100 align-items-center justify-content-between">
                <h4 class="card-title fw-bold">Category Management</h4>
 
            </div>
        </div>
        <div class="card-body">
            <div class="container-fluid">
                <div id="list" class="list-group">
                    {% for category in categories %}
                    <a href="{% url 'category-post' category.id %}" class="list-group-item list-group-item-action">
                        <h4><b>{{ category.name }}</b></h4>
                        <hr>
                        <p>{{ category.description }}</p>
                    </a>
                    {% endfor %}
                </div>
                {% if not categories %}
                <center>No Category Listed Yet</center>
                {% endif %}
            </div>
        </div>
    </div>
</div>
{% endblock pageContent %}
myapp/admin.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//myapp/admin.py
import django
from django.contrib import admin
from myapp.models import Category, Post
from django_summernote.admin import SummernoteModelAdmin
 
# Register your models here.
admin.site.register(Category)
#admin.site.register(Post)
 
class PostAdmin(SummernoteModelAdmin):
    summernote_fields = ('blog_post',)
   
admin.site.register(Post, PostAdmin)
Run : C:\django\blogsite>python manage.py runserver

Saturday, June 25, 2022

Python Django Image Crop and Upload Crop Image with javascript ajax

Python Django Image Crop and Upload Crop Image with javascript ajax

Register App myapp

devproject/settings.py
1
2
3
4
5
6
7
8
9
10
11
//devproject/settings.py
    
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp', #add myapp
]
Creating View
myapp/views.py
1
2
3
4
5
6
7
8
9
10
11
12
//myapp/views.py
from django.shortcuts import render
from .forms import ImageForm
from django.http import JsonResponse
 
def index(request):
    form = ImageForm(request.POST or None, request.FILES or None)
    if form.is_valid():
        form.save()
        return JsonResponse({'message': 'works'})
    context = {'form': form}
    return render(request, 'main.html', context)
devproject/urls.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//devproject/urls.py
from django.contrib import admin
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('myapp.urls'))
]
 
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
myapp/urls.py
1
2
3
4
5
6
7
8
//myapp/urls.py
from django.urls import path
  
from . import views
  
urlpatterns = [
    path('', views.index, name='index'),
]
Make Migrations
Run the commands below to make migrations:
python manage.py makemigrations
python manage.py migrate
C:\django\devproject>python manage.py makemigrations
C:\django\devproject>python manage.py migrate
myapp/models.py
1
2
3
4
5
6
7
8
9
//myapp/models.py
from django.db import models
 
class Image(models.Model):
    file = models.ImageField(upload_to='images')
    uploaded = models.DateTimeField(auto_now_add=True)
 
    def __str__(self):
        return str(self.pk)
myapp/forms.py
1
2
3
4
5
6
7
8
//myapp/forms.py
from django import forms
from .models import Image
 
class ImageForm(forms.ModelForm):
    class Meta:
        model = Image
        fields = ('file',)
Bootstrap 5
https://getbootstrap.com/docs/5.0/getting-started/introduction/
https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css

Jquery
https://jquery.com/download/
CDN : jsDelivr CDN
https://www.jsdelivr.com/package/npm/jquery
https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js

Cropper
A simple jQuery image cropping plugin.
https://fengyuanchen.github.io/cropper/
CDN : https://cdnjs.com/libraries/cropper
myapp/templates/home.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//myapp/templates/home.html
{% extends 'base.html' %}
 
{% block content %}
<div class="container py-5">
    <header class="text-white text-center">
        <h1 class="display-4">Python Django Image Crop and Upload Crop Image with javascript ajax</h1>
    </header>
 
    <div class="row py-4">
        <div class="col-lg-6 mx-auto">
            <div class="input-group mb-3 px-4 py-2 rounded-pill bg-white shadow-sm">
                <form action="" id="image-form" style="padding-top:10px;">
                    {% csrf_token %}
                    {{form.as_p}}
                </form>
            </div>
            <div id="alert-box"></div>
            <div class="image-area mb-3"><div id="image-box"></div></div>
            <button class="btn btn-primary mt-3 not-visible" id="confirm-btn">Confirm</button>
        </div>
    </div>
</div>
<style>
#upload {
    opacity: 0;
}
.image-area {
    border: 2px dashed rgba(255, 255, 255, 0.7);
    padding: 1rem;
}
body {
    min-height: 100vh;
    background-color: #7b9a75;
    background-image: linear-gradient(147deg, #7b9a75 0%, #d7dde8 100%);
}
</style>   
{% endblock content %}
myapp/templates/base.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//myapp/templates/base.html
{% load static %}
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="shortcut icon" type="image/png" href="{% static 'favicon.ico' %}"/>
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"/>
 
    <!-- jquery -->
 
    <!-- cropper -->
    <title>Python Django Image Crop and Upload Crop Image with javascript ajax</title>
<style>
.not-visible {
    display: none;
}
</style>
</head>
<body>
    <div class="container">
        {% block content %}
        {% endblock content %}
    </div>
 
<script>
const alertBox = document.getElementById('alert-box')
const imageBox = document.getElementById('image-box')
const imageForm = document.getElementById('image-form')
const confirmBtn = document.getElementById('confirm-btn')
const input = document.getElementById('id_file')
 
const csrf = document.getElementsByName('csrfmiddlewaretoken')
 
input.addEventListener('change', ()=>{
    alertBox.innerHTML = ""
    confirmBtn.classList.remove('not-visible')
    const img_data = input.files[0]
    const url = URL.createObjectURL(img_data)
 
    imageBox.innerHTML = `<img src="${url}" id="image" width="700px">`
    var $image = $('#image')
    console.log($image)
 
    $image.cropper({
        aspectRatio: 16 / 9,
        crop: function(event) {
            console.log(event.detail.x);
            console.log(event.detail.y);
            console.log(event.detail.width);
            console.log(event.detail.height);
            console.log(event.detail.rotate);
            console.log(event.detail.scaleX);
            console.log(event.detail.scaleY);
        }
    });
     
    var cropper = $image.data('cropper');
    confirmBtn.addEventListener('click', ()=>{
        cropper.getCroppedCanvas().toBlob((blob) => {
            console.log('confirmed')
            const fd = new FormData();
            fd.append('csrfmiddlewaretoken', csrf[0].value)
            fd.append('file', blob, 'my-image.png');
 
            $.ajax({
                type:'POST',
                url: imageForm.action,
                enctype: 'multipart/form-data',
                data: fd,
                success: function(response){
                    console.log('success', response)
                    alertBox.innerHTML = `<div class="alert alert-success" role="alert">
                                            Successfully saved and cropped the selected image
                                        </div>`
                },
                error: function(error){
                    console.log('error', error)
                    alertBox.innerHTML = `<div class="alert alert-danger" role="alert">
                                            Ups...something went wrong
                                        </div>`
                },
                cache: false,
                contentType: false,
                processData: false,
            })
        })
    })
})
</script>
</body>
</html>
Run : C:\django\devsite>python manage.py runserver

Tuesday, June 21, 2022

Python Django Dependent Chained Dropdown List - Dynamic Dependent Select Box using jQuery Ajax

Python Django Dependent Chained Dropdown List - Dynamic Dependent Select Box using jQuery Ajax

Register App myapp
1
2
3
4
5
6
7
8
9
10
11
//devproject/settings.py
   
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp', #add myapp
]
Creating View
myapp/views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//myapp/views.py
   
from django.shortcuts import render, redirect, get_object_or_404
 
from .forms import MemberCreationForm
from .models import Member, City
 
def create_view(request):
    form = MemberCreationForm()
    if request.method == 'POST':
        form = MemberCreationForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('add')
    return render(request, 'home.html', {'form': form})
 
def update_view(request, pk):
    member = get_object_or_404(Member, pk=pk)
    form = MemberCreationForm(instance=member)
    if request.method == 'POST':
        form = MemberCreationForm(request.POST, instance=member)
        if form.is_valid():
            form.save()
            return redirect('change', pk=pk)
    return render(request, 'home.html', {'form': form})
 
# AJAX
def load_cities(request):
    country_id = request.GET.get('country_id')
    cities = City.objects.filter(country_id=country_id).all()
    return render(request, 'city_dropdown_list_options.html', {'cities': cities})
devproject/urls.py
1
2
3
4
5
6
7
8
9
//devproject/urls.py
   
from django.contrib import admin
from django.urls import path, include
 
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('myapp.urls'))
]
myapp/urls.py
1
2
3
4
5
6
7
8
9
10
11
12
//myapp/urls.py
   
from django.urls import path
 
from . import views
 
urlpatterns = [
    path('member/add/', views.create_view, name='add'),
    path('member/<int:pk>/', views.update_view, name='change'),
 
    path('member/ajax/load-cities/', views.load_cities, name='ajax_load_cities'), # AJAX
]
myapp/forms.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//myapp/forms.py
   
from django import forms
 
from myapp.models import Member, City
 
class MemberCreationForm(forms.ModelForm):
    class Meta:
        model = Member
        fields = '__all__'
        widgets = { 'name': forms.TextInput(attrs={ 'class': 'form-control' }),
            'email': forms.EmailInput(attrs={ 'class': 'form-control' }),
            'country': forms.Select(attrs={ 'class': 'form-select' }),
            'city': forms.Select(attrs={ 'class': 'form-select' }),
        }   
 
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['city'].queryset = City.objects.none()
 
        if 'country' in self.data:
            try:
                country_id = int(self.data.get('country'))
                self.fields['city'].queryset = City.objects.filter(country_id=country_id).order_by('name')
            except (ValueError, TypeError):
                pass  # invalid input from the client; ignore and fallback to empty City queryset
        elif self.instance.pk:
            self.fields['city'].queryset = self.instance.country.city_set.order_by('name')
Make Migrations
Run the commands below to make migrations:
python manage.py makemigrations
python manage.py migrate
C:\django\devproject>python manage.py makemigrations
C:\django\devproject>python manage.py migrate
myapp/models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//myapp/models.py
   
from django.db import models
 
class Country(models.Model):
    name = models.CharField(max_length=40)
 
    def __str__(self):
        return self.name
 
class City(models.Model):
    country = models.ForeignKey(Country, on_delete=models.CASCADE)
    name = models.CharField(max_length=40)
 
    def __str__(self):
        return self.name
 
class Member(models.Model):
    name = models.CharField(max_length=124)
    email = models.CharField(max_length=125)
    country = models.ForeignKey(Country, on_delete=models.SET_NULL, blank=True, null=True)
    city = models.ForeignKey(City, on_delete=models.SET_NULL, blank=True, null=True)
 
    def __str__(self):
        return self.name
myapp/admin.py
1
2
3
4
5
6
7
8
9
10
//myapp/admin.py
   
from django.contrib import admin
 
# Register your models here.
from myapp.models import City, Country, Member
 
admin.site.register(City)
admin.site.register(Country)
admin.site.register(Member)
Bootstrap 5
https://getbootstrap.com/docs/5.0/getting-started/introduction/
https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css

Jquery
https://jquery.com/download/
CDN : jsDelivr CDN
https://www.jsdelivr.com/package/npm/jquery
https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js

myapp/templates/home.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//myapp/templates/home.html
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Dependent Dropdown in Django</title>
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container"> <p><h1>Python Django Dependent Chained Dropdown List - Dynamic Dependent Select Box using jQuery Ajax</h1></p>
    <div class="card">
        <div class="card-header">
            Member Form
        </div>
        <div class="card-body">
            <form method="post" id="MemberForm" data-cities-url="{% url 'ajax_load_cities' %}" class="form-horizontal" role="form">
                {% csrf_token %}
                {{ form.as_p }}
                <input type="submit" value="Submit" class="btn btn-info">
            </form>
        </div>
      </div>
</div>
<script>
    $("#id_country").change(function () {
        const url = $("#MemberForm").attr("data-cities-url");  // get the url of the `load_cities` view
        const countryId = $(this).val();  // get the selected country ID from the HTML input
 
        $.ajax({                       // initialize an AJAX request
            url: url,                    // set the url of the request (= /members/ajax/load-cities/ )
            data: {
                'country_id': countryId       // GET parameters
            },
            success: function (data) {  
                $("#id_city").html(data);
            }
        });
 
    });
</script>
</body>
</html>
myapp/templates/city_dropdown_list_options.html
1
2
3
4
5
//myapp/templates/city_dropdown_list_options.html
<option value="">---------</option>
{% for city in cities %}
<option value="{{ city.pk }}">{{ city.name }}</option>
{% endfor %}
Run : C:\django\devsite>python manage.py runserver

Sunday, June 19, 2022

Python Django Load More Data using jquery Ajax

Python Django Load More Data using jquery Ajax

Register App
1
2
3
4
5
6
7
8
9
10
11
//blogsite/settings.py
   
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp', #add myapp
]
Creating View
Bootstrap 4 
https://getbootstrap.com/docs/4.5/getting-started/introduction/
https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.cs

Jquery
https://jquery.com/download/
CDN : jsDelivr CDN
https://www.jsdelivr.com/package/npm/jquery
https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js
myapp/templates/base.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#myapp/templates/base.html
{% load static %}
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
    <title>Python Django Load More Data using jquery Ajax</title>
</head>
<body>
<div class="container mt-3">
    <p><h1>Python Django Load More Data using jquery Ajax</h1></p>
    {% block content %}{% endblock content %}
</div>
<script>
const postsBox = document.getElementById('posts-box')
console.log(postsBox)
const spinnerBox = document.getElementById('spinner-box')
const loadBtn = document.getElementById('load-btn')
const loadBox = document.getElementById('loading-box')
let visible = 3
 
const handleGetData = () => {
    $.ajax({
        type: 'GET',
        url: `/posts-json/${visible}/`,
        success: function(response){
            maxSize = response.max
            const data = response.data
            spinnerBox.classList.remove('not-visible')
            setTimeout(()=>{
                spinnerBox.classList.add('not-visible')
                data.map(post=>{
                    console.log(post.id)
                    postsBox.innerHTML += `<div class="card p-3 mt-3 mb-3">
                                                ${post.post_title}
                                                <br>
                                                ${post.post_content}
                                            </div>`
                })
                if(maxSize){
                    console.log('done')
                    loadBox.innerHTML = "<h4>No more posts to load</h4>"
                }
            }, 500)
        },
        error: function(error){
            console.log(error)
        }
    })
}
 
handleGetData()
 
loadBtn.addEventListener('click', ()=>{
    visible += 3
    handleGetData()
})
</script>
<style>
    .not-visible {
        display: none;
    }
</style>   
</body>
</html>
myapp/templates/main.html
1
2
3
4
5
6
7
8
9
10
11
12
#myapp/templates/main.html
{% extends "base.html" %}
 
{% block content %}
    <div id="posts-box"></div>
    <div id="spinner-box" class="not-visible">
        <div class="spinner-border text-primary" role="status"></div>
    </div>
    <div id="loading-box">
        <button class="btn btn-primary" id="load-btn">Load more</button>
    </div>
{% endblock content %}
Model
Make Migrations
Run the commands below to make migrations:
python manage.py makemigrations
python manage.py migrate
C:\django\blogsite>python manage.py makemigrations
C:\django\blogsite>python manage.py migrate
myapp/models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#myapp/models.py
from django.db import models
 
# Create your models here.
class Post(models.Model):
    post_title = models.CharField(max_length=200)
    post_content = models.TextField()
    created = models.DateTimeField(auto_now_add=True)
 
    def __str__(self):
        return str(self.pk)
 
    class Meta:
        ordering = ('-created',)
myapp/views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#myapp/views.py
from django.shortcuts import render
from django.views.generic import View, TemplateView
from .models import Post
from django.http import JsonResponse
 
class MainView(TemplateView):
    template_name = 'main.html'
 
class PostJsonListView(View):
    def get(self, *args, **kwargs):
        print(kwargs)
        upper = kwargs.get('num_posts')
        lower = upper - 3
        posts = list(Post.objects.values()[lower:upper])
        posts_size = len(Post.objects.all())
        max_size = True if upper >= posts_size else False
        return JsonResponse({'data': posts, 'max': max_size}, safe=False)  
blogsite/urls.py
1
2
3
4
5
6
7
8
9
10
11
#blogsite/urls.py
from django.contrib import admin
from django.urls import path
 
from myapp.views import MainView, PostJsonListView
 
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', MainView.as_view(), name='main-view'),
    path('posts-json/<int:num_posts>/', PostJsonListView.as_view(), name='posts-json-view'),
]
Run : C:\django\blogsite>python manage.py runserver

Friday, June 17, 2022

Python Django Ajax Login Register User Authentication with Profile and Upload Photo

Python Django Ajax Login Register User Authentication with Profile and Upload Photo

Register App myapp and static file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//blogsite/settings.py
import os
  
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp', #add myapp
]
  
# Static files (CSS, JavaScript, Images)
  
STATIC_URL = 'static/'
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
STATICFILES_DIRS = (
    # location of your application, should not be public web accessible
    './static',
)
  
LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/login'
Creating View
myapp/views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//myapp/views.py
from django.shortcuts import render, redirect
from django.http import HttpResponse
from django.urls import reverse
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.decorators import login_required
import json
from django.contrib.auth.models import User
from django.contrib import messages
 
from myapp.models import UserProfile
from myapp.forms import UserRegistration, UpdateProfile, UpdateProfileMeta, UpdateProfileAvatar, AddAvatar
 
context = {
    'page_title' : 'Blog Site'
}
 
# Create your views here.
def home(request):
    context['page_title'] = 'Home'
    return render(request, 'home.html',context)
 
def login_user(request):
    logout(request)
    resp = {"status":'failed','msg':''}
    username = ''
    password = ''
    if request.POST:
        username = request.POST['username']
        password = request.POST['password']
 
        user = authenticate(username=username, password=password)
        if user is not None:
            if user.is_active:
                login(request, user)
                resp['status']='success'
            else:
                resp['msg'] = "Incorrect username or password"
        else:
            resp['msg'] = "Incorrect username or password"
    return HttpResponse(json.dumps(resp),content_type='application/json')
 
#Logout
def logoutuser(request):
    logout(request)
    return redirect('/')
 
def registerUser(request):
    user = request.user
    if user.is_authenticated:
        return redirect('home-page')
    context['page_title'] = "Register User"
    if request.method == 'POST':
        data = request.POST
        form = UserRegistration(data)
        if form.is_valid():
            form.save()
            newUser = User.objects.all().last()
            try:
                profile = UserProfile.objects.get(user = newUser)
            except:
                profile = None
            if profile is None:
                UserProfile(user = newUser, dob= data['dob'], contact= data['contact'], address= data['address'], avatar = request.FILES['avatar']).save()
            else:
                UserProfile.objects.filter(id = profile.id).update(user = newUser, dob= data['dob'], contact= data['contact'], address= data['address'])
                avatar = AddAvatar(request.POST,request.FILES, instance = profile)
                if avatar.is_valid():
                    avatar.save()
            username = form.cleaned_data.get('username')
            pwd = form.cleaned_data.get('password1')
            loginUser = authenticate(username= username, password = pwd)
            login(request, loginUser)
            return redirect('home-page')
        else:
            context['reg_form'] = form
 
    return render(request,'register.html',context)
 
@login_required
def profile(request):
    context = {
        'page_title':"My Profile"
    }
 
    return render(request,'profile.html',context)
 
@login_required
def update_profile(request):
    context['page_title'] = "Update Profile"
    user = User.objects.get(id= request.user.id)
    profile = UserProfile.objects.get(user= user)
    context['userData'] = user
    context['userProfile'] = profile
    if request.method == 'POST':
        data = request.POST
        form = UpdateProfile(data, instance=user)
        if form.is_valid():
            form.save()
            form2 = UpdateProfileMeta(data, instance=profile)
            if form2.is_valid():
                form2.save()
                messages.success(request,"Your Profile has been updated successfully")
                return redirect("profile")
            else:
                context['form2'] = form2
        else:
            context['form1'] = form
            form = UpdateProfile(instance=request.user)
    return render(request,'update_profile.html',context)
 
 
@login_required
def update_avatar(request):
    context['page_title'] = "Update Avatar"
    user = User.objects.get(id= request.user.id)
    context['userData'] = user
    context['userProfile'] = user.profile
    img = user.profile.avatar.url
 
    context['img'] = img
    if request.method == 'POST':
        form = UpdateProfileAvatar(request.POST, request.FILES,instance=user)
        if form.is_valid():
            form.save()
            messages.success(request,"Your Profile has been updated successfully")
            return redirect("profile")
        else:
            context['form'] = form
            form = UpdateProfileAvatar(instance=user)
    return render(request,'update_avatar.html',context)
blogsite/urls.py
1
2
3
4
5
6
7
8
9
10
11
//blogsite/urls.py
from django.contrib import admin
from django.urls import include, path
  
from django.conf import settings
from django.conf.urls.static import static
  
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('myapp.urls')),
] + static(settings.MEDIA_URL, document_root = settings.MEDIA_ROOT)
myapp/urls.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//myapp/urls.py
from . import views
from django.contrib import admin
from django.urls import path, re_path
from django.contrib.auth import views as auth_views
from django.views.generic.base import RedirectView
 
urlpatterns = [
    path('redirect-admin', RedirectView.as_view(url="/admin"),name="redirect-admin"),
    path('', views.home, name="home-page"),
    path('login',auth_views.LoginView.as_view(template_name="login.html",redirect_authenticated_user = True),name='login'),
    path('userlogin', views.login_user, name="login-user"),
    path('user-register', views.registerUser, name="register-user"),
    path('logout',views.logoutuser,name='logout'),
    path('profile',views.profile,name='profile'),
    path('update-profile',views.update_profile,name='update-profile'),
    path('update-avatar',views.update_avatar,name='update-avatar'),
]
Make Migrations
Run the commands below to make migrations:
python manage.py makemigrations
python manage.py migrate
C:\django\blogsite>python manage.py makemigrations
C:\django\blogsite>python manage.py migrate
myapp/models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//myapp/models.py
from django.db import models
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.models import User
 
class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE,related_name='profile')
    contact = models.CharField(max_length=250)
    dob = models.DateField(blank=True, null = True)
    address = models.TextField(blank=True, null = True)
    avatar = models.ImageField(blank=True, null = True, upload_to= 'images/')
    user_type = models.IntegerField(default = 2)
 
 
    def __str__(self):
        return self.user.username
 
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)
 
def save_user_profile(sender, instance, **kwargs):
    print(instance)
    try:
        profile = UserProfile.objects.get(user = instance)
    except Exception as e:
        UserProfile.objects.create(user=instance)
    instance.profile.save()
myapp/forms.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
//myapp/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
 
from django.contrib.auth.models import User
from myapp.models import UserProfile
 
class UserRegistration(UserCreationForm):
    email = forms.EmailField(max_length=250,help_text="The email field is required.")
    first_name = forms.CharField(max_length=250,help_text="The First Name field is required.")
    last_name = forms.CharField(max_length=250,help_text="The Last Name field is required.")
 
    class Meta:
        model = User
        fields = ('email', 'username', 'password1', 'password2', 'first_name', 'last_name')
     
 
    def clean_email(self):
        email = self.cleaned_data['email']
        try:
            user = User.objects.get(email = email)
        except Exception as e:
            return email
        raise forms.ValidationError(f"The {user.email} mail is already exists/taken")
 
    def clean_username(self):
        username = self.cleaned_data['username']
        try:
            user = User.objects.get(username = username)
        except Exception as e:
            return username
        raise forms.ValidationError(f"The {user.username} mail is already exists/taken")
 
class UpdateProfile(forms.ModelForm):
    username = forms.CharField(max_length=250,help_text="The Username field is required.")
    email = forms.EmailField(max_length=250,help_text="The Email field is required.")
    first_name = forms.CharField(max_length=250,help_text="The First Name field is required.")
    last_name = forms.CharField(max_length=250,help_text="The Last Name field is required.")
    current_password = forms.CharField(max_length=250)
 
    class Meta:
        model = User
        fields = ('email', 'username','first_name', 'last_name')
 
    def clean_current_password(self):
        if not self.instance.check_password(self.cleaned_data['current_password']):
            raise forms.ValidationError(f"Password is Incorrect")
 
    def clean_email(self):
        email = self.cleaned_data['email']
        try:
            user = User.objects.exclude(id=self.cleaned_data['id']).get(email = email)
        except Exception as e:
            return email
        raise forms.ValidationError(f"The {user.email} mail is already exists/taken")
 
    def clean_username(self):
        username = self.cleaned_data['username']
        try:
            user = User.objects.exclude(id=self.cleaned_data['id']).get(username = username)
        except Exception as e:
            return username
        raise forms.ValidationError(f"The {user.username} mail is already exists/taken")
        
 
class UpdateProfileMeta(forms.ModelForm):
    dob = forms.DateField(help_text="The Birthday field is required.")
    contact = forms.CharField(max_length=250,help_text="The Contact field is required.")
    address = forms.CharField(help_text="The Contact field is required.")
 
    class Meta:
        model = UserProfile
        fields = ('dob', 'contact', 'address')
 
class UpdateProfileAvatar(forms.ModelForm):
    avatar = forms.ImageField(help_text="The Avatar field is required.")
    current_password = forms.CharField(max_length=250)
 
    class Meta:
        model = UserProfile
        fields = ('avatar',)
     
    def __init__(self,*args, **kwargs):
        self.user = kwargs['instance']
        kwargs['instance'] = self.user.profile
        super(UpdateProfileAvatar,self).__init__(*args, **kwargs)
 
    def clean_current_password(self):
        if not self.user.check_password(self.cleaned_data['current_password']):
            raise forms.ValidationError("Password is Incorrect")
 
 
class AddAvatar(forms.ModelForm):
    avatar = forms.ImageField(help_text="The Avatar field is required.")
    class Meta:
        model = UserProfile
        fields = ('avatar',)
myapp/admin.py
1
2
3
4
5
6
7
//myapp/admin.py
import django
from django.contrib import admin
from myapp.models import UserProfile
 
# Register your models here.
admin.site.register(UserProfile)
myapp/templates/home.html
1
2
3
4
5
6
7
8
9
10
11
12
//myapp/templates/home.html
{% extends "base.html" %}
 
{% block pageContent %}
<section class="text-center">
    <h4 class="mb-5"><strong>All Posts</strong></h4>
    <div class="row">
        <h1>Homepage - Python Django Ajax Login Register User Authentication with Profile and Upload Photo </h1>
    </div>
</section>
</div> 
{% endblock pageContent %}
myapp/templates/base.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//myapp/templates/base.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    {% if page_title %}
    <title>{{ page_title }} | Blog Site</title>
    {% else %}
    <title>Blog Site</title>
    {% endif %}
    <link rel="stylesheet" href="{% static 'blogApp/assets/bootstrap/css/bootstrap.min.css' %}">
 
</head>
<body>
    {% block TopNavigation %} {% include "TopNavigation.html" %} {% endblock TopNavigation %}
    <main class="my-5">
        <div class="container" style="padding-top: 50px;">
            {% block pageContent %} {% endblock pageContent %}
        </div>
    </main>
    {% block ScriptBlock %} {% endblock ScriptBlock %}
    <footer class="bg-light text-lg-start">
        <div class="text-center p-3" style="background-color: rgba(0, 0, 0, 0.2);">
            © {% now 'Y' %} Copyright:
            <a class="text-dark" href="#" target="_blank">Cairocoders</a>
        </div>
    </footer>
 
</body>
</html>
myapp/templates/login.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
//myapp/templates/login.html
{% extends 'base.html' %}
 
{% load static %}
 
{% block pageContent %}
<div class="row justify-content-center">
    <div class="col-lg-4 col-md-6 col-sm-12 col-xs-12">
        <p><img src="{{ MEDIA_URL}}/media/images/banner1.png" class="img-fluid" /></p>
        <div class="card card-default rounded-0 shadow">
            <div class="card-header">
                <h4 class="card-title"><b>Login</b></h4>
            </div>
            <div class="card-body">
                <div class="container-fluid">
                    <form id="login-user">
                        {% csrf_token %}
                        <div class="mdc-layout-grid">
                            <div class="mdc-layout-grid__inner">
                                <div class="form-group mb-3">
                                    <label for="username" class="control-label">Username</label>
                                    <input type="text" class="form-control rounded-0" name="username" id="username" required="required">
                                </div>
                                <div class="form-group mb-3">
                                    <label for="password" class="control-label">Password</label>
                                    <input type="password" class="form-control rounded-0" name="password" id="password" required="required">
                                </div>
                                <div class="form-group mb-3">
                                    <div class="d-flex w-100 justify-content-between align-items-center">
                                        <div class="col-md-4"><a href="{% url 'register-user' %}">Signup</a></div>
                                        <button class="btn btn-sm rounded-0 btn-primary">Login</button>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock pageContent %}
 
{% block ScriptBlock %}
<script>
    $(function() {
        $('#login-user').submit(function(e) {
            e.preventDefault();
            var _this = $(this)
            $('.err-msg').remove();
            var el = $('<div>')
            el.addClass("alert alert-danger err-msg")
            el.hide()
            if (_this[0].checkValidity() == false) {
                _this[0].reportValidity();
                return false;
            }
            $.ajax({
                headers: {
                    "X-CSRFToken": '{{csrf_token}}'
                },
                url: "{% url 'login-user' %}",
                data: new FormData($(this)[0]),
                cache: false,
                contentType: false,
                processData: false,
                method: 'POST',
                type: 'POST',
                dataType: 'json',
                error: err => {
                    console.log(err)
                    alert_toast("An error occured", 'error');
                },
                success: function(resp) {
                    if (typeof resp == 'object' && resp.status == 'success') {
                        el.removeClass("alert alert-danger err-msg")
                        location.href = "{% url 'home-page' %}"
                    } else if (resp.status == 'failed' && !!resp.msg) {
                        el.text(resp.msg)
                    } else {
                        el.text("An error occured", 'error');
                        console.err(resp)
                    }
                    _this.prepend(el)
                    el.show('slow')
                }
            })
        })
    })
</script>
{% endblock ScriptBlock %}
myapp/templates/TopNavigation.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//myapp/templates/TopNavigation.html
{% load static %}
<header>
    <nav class="navbar navbar-expand-lg navbar-light bg-white fixed-top">
        <div class="container">
            <a class="navbar-brand" target="_blank" href="#">Cairocoders</a>
            <button class="navbar-toggler" type="button" data-mdb-toggle="collapse" data-mdb-target="#navbarExample01" aria-controls="navbarExample01" aria-expanded="false" aria-label="Toggle navigation">
            <i class="fas fa-bars"></i>
            </button>
            <div class="collapse navbar-collapse" id="navbarExample01">
                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                    <li class="nav-item active">
                        <a class="nav-link" aria-current="page" href="{% url 'home-page' %}">Home</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#">All Categories</a>
                    </li>
                </ul>
 
                <ul class="navbar-nav d-flex flex-row">
                    {% if user.id %}
                    <li class="nav-item me-3 me-lg-0">
                        <a class="nav-link" href="{% url 'profile' %}">
                            <span class="me-1"><img src="{{ user.profile.avatar.url }}" width="30" height="30" alt="User Avatar" id="user-avatar" class="img-thumbnail rounded-circle"></span>Howdy, {{ user.first_name }} {{user.last_name}}
                        </a>
                    </li>
                    <li class="nav-item me-3 me-lg-0">
                        <a class="nav-link" href="{% url 'logout' %}">Logout</a>
                    </li>
                    {% else %}
                    <li class="nav-item me-3 me-lg-0">
                        <a class="nav-link" href="{% url 'login' %}">Login</a>
                    </li>
                    {% endif %}
                </ul>
            </div>
        </div>
    </nav>
</header>
myapp/templates/register.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
//myapp/templates/register.html
{% extends 'base.html' %}
{% load static %}
 
{% block pageContent %}
<div class="row justify-content-center">
    <div class="col-lg-4 col-md-6 col-sm-12 col-xs-12">
        <div class="card card-default rounded-0 shadow">
            <div class="card-header">
                <h4 class="card-title"><b>Create New User</b></h4>
            </div>
            <div class="card-body">
                <div class="container-fluid">
                    <form id="register-user" method="POST" action="{% url 'register-user' %}" enctype="multipart/form-data">
                        {% csrf_token %}
                        <div class="mdc-layout-grid">
                            <div class="mdc-layout-grid__inner">
                                <div class="form-group mb-3">
                                    <label for="first_name" class="control-label">First Name</label>
                                    <input type="text" class="form-control rounded-0" name="first_name" id="first_name" value="{{ reg_form.data.first_name }}" required="required">
                                </div>
                                <div class="form-group mb-3">
                                    <label for="last_name" class="control-label">Last Name</label>
                                    <input type="text" class="form-control rounded-0" name="last_name" id="last_name" value="{{ reg_form.data.last_name }}" required="required">
                                </div>
                                <div class="form-group mb-3">
                                    <label for="dob" class="control-label">Birthdate</label>
                                    <input type="date" class="form-control rounded-0" name="dob" id="dob" value="{{ reg_form.data.dob }}" required="required">
                                </div>
                                <div class="form-group mb-3">
                                    <label for="contact" class="control-label">Contact</label>
                                    <input type="text" class="form-control rounded-0" name="contact" id="contact" value="{{ reg_form.data.contact }}" required="required">
                                </div>
                                <div class="form-group mb-3">
                                    <label for="address" class="control-label">Address</label>
                                    <textarea rows="3" class="form-control rounded-0" name="address" id="address" required="required">{{ reg_form.data.address }}</textarea>
                                </div>
                                <div class="form-group mb-3">
                                    <label for="email" class="control-label">Email</label>
                                    <input type="email" class="form-control rounded-0" name="email" id="email" value="{{ reg_form.data.email }}" required="required">
                                </div>
                                <div class="form-group mb-3">
                                    <label for="username" class="control-label">Username</label>
                                    <input type="text" class="form-control rounded-0" name="username" id="username" value="{{ reg_form.data.username }}" required="required">
                                </div>
                                <div class="form-group mb-3">
                                    <label for="password1" class="control-label">Password</label>
                                    <input type="password" class="form-control rounded-0" name="password1" id="password1" value="{{ reg_form.data.password1 }}" required="required">
                                </div>
                                <div class="form-group mb-3">
                                    <label for="password2" class="control-label">Confirm Password</label>
                                    <input type="password" class="form-control rounded-0" name="password2" id="password2" value="{{ reg_form.data.password2 }}" required="required">
                                </div>
 
                                <div class="form-group mb-3">
                                    <label for="avatar" class="control-label">User Avatar</label>
                                    <input type="file" class="form-control rounded-0" name="avatar" id="avatar" accept="images/*" required="required">
                                </div>
                                {% for field in reg_form %} {% for error in field.errors %}
                                <div class="alert alert-danger my-2">
                                    <p>{{ error }}</p>
                                </div>
                                {% endfor %} {% endfor %}
                                <div class="form-group mb-3">
                                    <div class="d-flex w-100 justify-content-end">
                                        <button class="btn btn-sm rounded-0 btn-primary col-4">Sign Up</button>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock pageContent %}
myapp/templates/profile.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//myapp/templates/profile.html
{% extends 'base.html' %}
 
{% block pageContent%}
<div class="container">
    <div class="row justify-content-center">
        <div class="col-lg-7 col-md-10 col-sm-12 col-xs-12">
            <div class="card shadow rounded-0">
                <div class="card-header">
                    <div class="h5 card-title fw-bold">My Profile</div>
                </div>
                <div class="card-body">
                    <div class="container-fluid">
                        <div class="row">
                            <div class="col-md-4">
                                <img src="{{ user.profile.avatar.url }}" alt="User-image" id="user-img-profile" class="img-thumbnail img-fluid">
                                <center><a href="{% url 'update-avatar'%}">Update Avatar</a></center>
                            </div>
                            <div class="col-md-8">
                                <dl>
                                    <dt class="text-muted">Fullname</dt>
                                    <dd class="ps-4">{{ user.first_name }} {{ user.last_name }}</dd>
                                    <dt class="text-muted">Birthday</dt>
                                    <dd class="ps-4">{{ user.profile.dob|date:"F d, Y" }}</dd>
                                    <dt class="text-muted">Contact #</dt>
                                    <dd class="ps-4">{{ user.profile.contact }}</dd>
                                    <dt class="text-muted">Address</dt>
                                    <dd class="ps-4">{{ user.profile.address }}</dd>
                                    <dt class="text-muted">Email</dt>
                                    <dd class="ps-4">{{ user.email }}</dd>
                                    <dt class="text-muted">Username</dt>
                                    <dd class="ps-4">{{ user.username }}</dd>
                                </dl>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="card-footer">
                    <div class="d-flex w-100 text-end justify-content-end">
                        <a href="{% url 'update-profile' %}" class="btn btn btn-primary btn-sm bg-gradient rounded-0">Update Profile</a>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock pageContent%}
myapp/templates/update_profile.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//myapp/templates/update_profile.html
{% extends 'base.html' %}
 
{% load static %}
 
{% block pageContent %}
<div class="row justify-content-center">
    <div class="col-lg-8 col-md-10 col-sm-12 col-xs-12">
        <div class="card card-default rounded-0 shadow">
            <div class="card-header">
                <h4 class="card-title"><b>Update Profile</b></h4>
            </div>
            <div class="card-body">
                <div class="container-fluid">
                    <form id="register-user" method="POST" action="{% url 'update-profile' %}" enctype="multipart/form-data">
                        {% csrf_token %}
                        <div class="mdc-layout-grid">
                            <div class="mdc-layout-grid__inner">
                                <div class="row">
                                    <div class="col-md-6">
                                        <div class="form-group mb-3">
                                            <label for="first_name" class="control-label">First Name</label>
                                            <input type="text" class="form-control rounded-0" name="first_name" id="first_name" value="{{ userData.first_name }}" required="required">
                                        </div>
                                        <div class="form-group mb-3">
                                            <label for="last_name" class="control-label">Last Name</label>
                                            <input type="text" class="form-control rounded-0" name="last_name" id="last_name" value="{{ userData.last_name }}" required="required">
                                        </div>
                                        <div class="form-group mb-3">
                                            <label for="dob" class="control-label">Birthdate</label>
                                            <input type="date" class="form-control rounded-0" name="dob" id="dob" value="{{ userProfile.dob|date:'Y-m-d' }}" required="required">
                                        </div>
                                        <div class="form-group mb-3">
                                            <label for="contact" class="control-label">Contact</label>
                                            <input type="text" class="form-control rounded-0" name="contact" id="contact" value="{{ userProfile.contact }}" required="required">
                                        </div>
                                        <div class="form-group mb-3">
                                            <label for="address" class="control-label">Address</label>
                                            <textarea rows="3" class="form-control rounded-0" name="address" id="address" required="required">{{ userProfile.address }}</textarea>
                                        </div>
                                    </div>
                                    <div class="col-md-6">
                                        <div class="form-group mb-3">
                                            <label for="email" class="control-label">Email</label>
                                            <input type="email" class="form-control rounded-0" name="email" id="email" value="{{ userData.email }}" required="required">
                                        </div>
                                        <div class="form-group mb-3">
                                            <label for="username" class="control-label">Username</label>
                                            <input type="text" class="form-control rounded-0" name="username" id="username" value="{{ userData.username }}" required="required">
                                        </div>
                                        <div class="form-group mb-3">
                                            <label for="current_password" class="control-label">Current Password</label>
                                            <input type="password" class="form-control rounded-0" name="current_password" id="current_password" required="required">
                                        </div>
                                    </div>
                                </div>
 
                                {% for field in form1 %} {% for error in field.errors %}
                                <div class="alert alert-danger my-2">
                                    <p>{{ error }}</p>
                                </div>
                                {% endfor %} {% endfor %} {% for field in form2 %} {% for error in field.errors %}
                                <div class="alert alert-danger my-2">
                                    <p>{{ error }}</p>
                                </div>
                                {% endfor %} {% endfor %}
                                <div class="form-group mb-3">
                                    <div class="d-flex w-100 justify-content-end">
                                        <button class="btn btn-sm rounded-0 btn-primary col-4">
                                                Update
                                            </button>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock pageContent %}
myapp/templates/update_avatar.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//myapp/templates/update_avatar.html
{% extends 'base.html' %}
 
{% load static %}
 
{% block pageContent %}
<div class="row justify-content-center">
    <div class="col-lg-8 col-md-10 col-sm-12 col-xs-12">
        <div class="card card-default rounded-0 shadow">
            <div class="card-header">
                <h4 class="card-title"><b>Update Profile</b></h4>
            </div>
            <div class="card-body">
                <div class="container-fluid">
                    <form id="register-user" method="POST" action="{% url 'update-avatar' %}" enctype="multipart/form-data">
                        {% csrf_token %}
                        <div class="mdc-layout-grid">
                            <div class="mdc-layout-grid__inner">
                                <div class="row">
                                    <div class="col-md-6">
                                        <img src="{{ img }}" alt="User-image" id="user-img-profile" class="img-thumbnail img-fluid">
                                    </div>
                                    <div class="col-md-6">
                                        <div class="form-group mb-3">
                                            <label for="avatar" class="control-label">Avatar</label>
                                            <input type="file" class="form-control rounded-0" name="avatar" id="avatar" accept="image/*" onchange="DisplayImage(this)" required="required">
                                        </div>
                                        <div class="form-group mb-3">
                                            <label for="current_password" class="control-label">Current Password</label>
                                            <input type="password" class="form-control rounded-0" name="current_password" id="current_password" required="required">
                                        </div>
                                    </div>
                                </div>
 
                                {% for field in form %} {% for error in field.errors %}
                                <div class="alert alert-danger my-2">
                                    <p>{{ error }}</p>
                                </div>
                                {% endfor %} {% endfor %}
                                <div class="form-group mb-3">
                                    <div class="d-flex w-100 justify-content-end">
                                        <button class="btn btn-sm rounded-0 btn-primary col-4">
                                                Update
                                            </button>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock pageContent %} {% block ScriptBlock %}
<script>
    function DisplayImage(input) {
        console.log(input.files)
        if (event.target.files[0]) {
            var reader = new FileReader();
            reader.onload = function() {
                var output = $('#user-img-profile');
                output.attr('src', reader.result)
            };
            reader.readAsDataURL(input.files[0]);
        }
 
    };
</script>
{% endblock ScriptBlock %}
Run : C:\django\blogsite>python manage.py runserver

Related Post