Getting started with Django

Introduction

Tutorial Creating an Attorney Website.

Lawyers are the First Line of Defense Against Tyranny.

To start with you need a lightweight platform that loads in milliseconds, because study after study has shown that speed is the #1 most important factor when it comes to selling online.

And since Python is the best programming language and Django the most secure platform, we will be building the platform using those.


Installing Python

First up I’ve already got Python installed on my computer. On any system, you can check by jumping into your shell and typing python. If your system responds to the effect of “cannot be found,” you need to download and install Python.

If Python is present and installed, you should be dropped into a new prompt that looks like three greater than symbols >>>, and from there you can start typing in Python code.


Installing Django

I'm going to open a terminal now and cd into my Desktop, to where I want to store my project. I’m on Linux. In Linux I can open a terminal and just type cd which stands for change directory and Desktop the name of the directory on my computer that I want to go into.

$ cd Desktop

I’m going to name the folder for my project law but you can call yours anything you want.

$ mkdir law

Now I’ll change directories and go into the folder I just created.

$ cd law

Now I’ll Create a virtual environment for my project. This will prevent my code from getting mixed in with another projects code or my computers code.

You can name it anything you want but I normally name mine env, short for environment.

So I’ll type ...

$ python3 -m venv env

Now I’m going to activate it ...

$ source env/bin/activate

You know its activated because of the parenthesis around the name env.

Now we are going to install pip and pip is a package-management system used to install and manage software packages written in Python.

$ pip install --upgrade pip

And now that we have pip installed we can install Django, I could install different versions of Django by putting the version number after Django.

So if there was a project that I was using that had an older version of Django and I wanted to use that version, I could do so by typing the version after it but what I want to do is use the latest version and the latest version is Django 5.0.3 and to install the latest version I only have to type in pip3 install Django.

$ pip3 install Django

Or

$ python -m pip install Django==5.0.6

Now with Django we have apps and projects. A project can have as many apps in it as needed and an app is nothing more than a chunk of code that performs a certain task.


Creating A Project

Before we can create an app we’ve got to have a project we can put it in. I’m going to call mine src for source because this will be where all our main files go.

You can name it anything you want but I don’t want to have to hunt through a bunch of common names when I’m looking for the source folder.

I’ll put a dot After src and this tells the system to install it here and not in a directory.

$ django-admin.py startproject src .

Let’s take a look at what files we’ve created.

What Django Creates:

In your src folder you should have:

  1. __init__.py: This file is empty, and its purpose is to tell your system that your src directory should be treated like a Python module. They can contain Python code, and can be imported by importing the name of the directory.
  2. asgi.py:
  3. settings.py:
  4. urls.py:
  5. wsgi.py:
  6. manage.py: Is a Python file that lets you run commands that allow you to control, run, and create your Django site.

Laying Out Settings

The next file I want to take a look at is settings.py, if you scroll down to INSTALLED_APPS you can see how you let the system know about any new apps you install.

By default, the INSTALLED_APPS has some apps already installed.

  1. 'django.contrib.admin',
  2. 'django.contrib.auth',
  3. 'django.contrib.contenttypes',
  4. 'django.contrib.sessions',
  5. 'django.contrib.messages',
  6. 'django.contrib.staticfiles',

Now since I’m using the terminal I can create more than one directory at a time so I’ll create a templates folder, apps folder, static folder, and a media folder all at the same time by typing.

python3 manage.py migrate 
$ python3 manage.py runserver

I’m telling it to create a folder called templates, apps, and media and then create a parent starting here called static with a child folder called img.

$ mkdir templates apps media -p ./static/img

Okay now there are a couple ways we could go about creating our templates folders. You can either create a templates folder inside each of your apps, this way you grab the app and get the design with it, or you can create one template folder and have all your templates inside that folder.

If all your template folders are in one folder then you could change the design by changing one folder. Change one folder and change the design site wide. A designer only has to touch one folder to change the design from one client to the next.

Other words, you could have a hundred different designs to choose from and you could change them site wide by swapping out one folder.

Now if you look you’ve just created a templates, apps, media, and static folder and inside the static folder you will see an img folder for your images. The static folder is where Django looks for images, css, java, and so on.

Now you can go inside your src folder, by going back to the terminal and typing in cd src.

$ mkdir apps media -p templates/{account,pages,tags,catalog,shop,cart,checkout,post,contact} static/img
$ cd src

Now what you want to do is split up your settings.py file into a production and development file, so you can keep your code clean and separate from the things you do in production, from the things you do while in development.

So you will make a new folder and you’ll call it settings by typing mkdir settings in your terminal.

$ mkdir settings

Then you can change directories and go into the settings folder you just created by typing in cd settings.

$ cd settings

I need to create an init file to let the system know I’m running python so I’ll type touch (like you are going to touch something and create it and then the name of what I want to create, with an under score, under score init underscore underscore .py.

$ touch __init__.py

Now I’m going to create my production and development folders. The idea will be to have a base file where the information that will be the same for production and development will go and then to have separate files for the things that are different.

So I’ll just say touch curly bracket base comma dev comma pro close curly bracket dot py.

$ touch {base,dev,pro}.py

Dev for development and pro for production.

Then I will cd back out of there to where my main.py folder is located by typing in my terminal cd space dot dot back slash dot dot back slash and hitting enter on the keyboard.

$ cd ../../

Now I’m going to remove all the comments from settings.py and copy everything that is left and paste it into base.py

At top of .dev and .pro

Now we need to tell the system where to find the rest of the information so at the top of both dev and pro put from dot base import star.

from .base import *

Then remove all the comments down to DEBUG and highlight

          DEBUG = True
ALLOWED_HOSTS = ["127.0.0.1"]

and do ctrl x and paste it at the top of dev and pro but change DEBUG to False on pro.

Laying Out Settings

Laying Out URL's

The next thing we want to do is set up our urls.py folder.

By default, the INSTALLED_APPS has some apps already installed.

from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls), ]

So we will remove all comments and import some code from our settings.

from django.conf import settings

Since we have images I’m going to import the static folder we created.

from django.conf.urls.static import static

I’ll leave the admin that is already here.

from django.contrib import admin

And I’ll add include with our path so we can send people to our apps.

from django.urls import path, include

I’ll leave the urlpatterns alone for now.

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

We don't need to run staticfiles when we're running a development server and DEBUG is set to True. Static and media files can be served directly so we are adding a couple lines to our main urls.py: to do that so under our url patterns we will add …

if settings.DEBUG: urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

And since we moved our settings.py into a settings folder and changed the name to dev, and pro we need to let Django know about this change and the way we go about doing that is by going onto the manage.py and adding .dev to the end of the cartturbo.settings.

When we go into production we will need to change that to pro.

def main():
              os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cartturbo.settings.dev')

Custom User Model

When you want people to interact with your website, rather it be to leave comments, make a purchase, or join a group, you need a way for them to register.

Now Django comes with a built-in model for handling this but its very limited in what you can do, so we are going to build our own. My biggest problem with Django's user model, is that it logins with the user name instead of the email.

I think the login being the email is better because often times you can't have the username you want, so you end up with different usernames, which you can easily forget. And another advantage is if I forget my password I click "reset password" and since my login is my password, I do not need to enter anything else.

We are going to create our first app, an app is nothing more than a chunk of code we are going to be plugging into our website. However, I want to keep all my apps together in an easy to find place, I don’t want to get confused with which folders are apps and which are something else, so I’m going to put all my apps in the folder I created earlier called apps.

Go to your terminal and cd into your apps folder.

$ cd apps

You do however need to make sure that your elements are properly nested: in the example above, we opened the p element first, then the strong element, therefore we have to close the strong element first, then the p. The following is incorrect:

<p>My cat is <strong>very grumpy.</p></strong>

The elements have to open and close correctly, so they are clearly inside or outside one another. If they overlap like above, then your web browser will try to make a best guess at what you were trying to say, and you may well get unexpected results. So don't do it!

Block versus inline elements

There are two important categories of elements in HTML which you should know about. They are block-level elements and inline elements.

  • Block-level elements form a visible block on a page — they will appear on a new line from whatever content went before it, and any content that goes after it will also appear on a new line. Block-level elements tend to be structural elements on the page that represent, for example, paragraphs, lists, navigation menus, footers, etc. A block-level element wouldn't be nested inside an inline element, but it might be nested inside another block-level element.
  • Inline elements are those that are contained within block-level elements and surround only small parts of the document’s content, not entire paragraphs and groupings of content. An inline element will not cause a new line to appear in the document; they would normally appear inside a paragraph of text, for example an <a> element (hyperlink) or emphasis elements such as <em> or <strong>.

Take the following example:

<em>first</em><em>second</em><em>third</em>

<p>fourth</p><p>fifth</p><p>sixth</p>

<em> is an inline element, so as you can see below, the first three elements sit on the same line as one another with no space in between. On the other hand, <p> is a block-level element, so each element appears on a new line, with space above and below each (the spacing is due to default CSS styling that the browser applies to paragraphs).

firstsecondthird

fourth

fifth

sixth

$ cd cartturbo
$ mkdir static settings
$ cd settings
$ touch __init__.py
$ touch {base,dev,pro}.py

main urls.py

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

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

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL,
                          document_root=settings.MEDIA_ROOT)
    urlpatterns += static(settings.STATIC_URL,
                          document_root=settings.STATIC_ROOT)

        

base.py

from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent


SECRET_KEY = 'django-insecure-&3iz&g*583g5*7(4jk7ndf#gtw#0uqprdgx6w1grj8ezl1s6l-'


DEFAULT_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]


THIRD_PARTY_APPS = [

]


LOCAL_APPS = [
  
]

INSTALLED_APPS = DEFAULT_APPS + LOCAL_APPS + THIRD_PARTY_APPS

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'cartturbo.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'cartturbo.wsgi.application'


DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}


AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True


STATIC_URL = 'static/'

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'


      

The current dev.py should look like this:

from .base import *

DEBUG = True

ALLOWED_HOSTS = []

      

The current pro.py should look like this:

from .base import *

DEBUG = False

ALLOWED_HOSTS = []

      
$ cd ../../
$ cd apps
$ django-admin startapp accounts

models.py.

$ cd accounts

from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager

class CustomManager(BaseUserManager):
    
    def create_superuser(self, email, username, password, **other_fields):
        other_fields.setdefault('is_staff', True)
        other_fields.setdefault('is_superuser', True)
        other_fields.setdefault('is_active', True)
        if other_fields.get('is_staff') is not True:
            raise ValueError(
                'Superuser must be assigned to is_staff=True')
        if other_fields.get('is_superuser') is not True:
            raise ValueError(
                'Superuser must be assigned to is_superuser=True')  
        return self.create_user(email,username,password,**other_fields)

    def create_user(self, email, username, password, **other_fields):
        if not email:
            raise ValueError(_('You must provide an email address'))
        email = self.normalize_email(email)
        user = self.model(email=email, username=username, **other_fields)
        user.set_password(password)
        user.save()
        return user

class User(AbstractBaseUser, PermissionsMixin):
    email       = models.EmailField(_('email address'), unique=True)
    username    = models.CharField(max_length=150, unique=False, default='')
    start_date  = models.DateTimeField(default=timezone.now)
    about       = models.TextField(_('about'), max_length=500, blank=True)
    is_staff    = models.BooleanField(default=False)
    is_active   = models.BooleanField(default=True)
    objects     = CustomManager()
    USERNAME_FIELD  ='email'
    REQUIRED_FIELDS =['username',]

    def __str__(self):
        return self.username

      

admin.py

from django.contrib import admin
from .models import User
from django.contrib.auth.admin import UserAdmin
from django.forms import TextInput, Textarea

class UserAdminConfig(UserAdmin):

    model = User
    search_fields = ('email', 'username',)
    list_filter = ('email', 'username', 'is_active', 'is_staff')
    ordering = ('-start_date',)
    list_display = ('email', 'username',
                    'is_active', 'is_staff')
    fieldsets = (
        (None, {'fields': ('email', 'username',)}),
        ('Permissions', {'fields': ('is_staff', 'is_active')}),
        ('Personal', {'fields': ('about',)}),
    )
    formfield_overrides = {
        User.about: {'widget': Textarea(attrs={'rows': 10, 'cols': 40})},
    }
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'username', 'password1', 'password2', 'is_active', 'is_staff')}
         ),
    )

admin.site.register(User, UserAdminConfig)
        

urls.py

from django.urls import path
from django.conf.urls import url
from . import views
from allauth.account.views import LoginView, SignupView 

app_name = 'accounts'

urlpatterns = [
    path('register/', views.register, name='register'),
    path('my_account/', views.my_account, name='my_account'),
    path('order_info/', views.order_info, name='order_info'),
    path('order_details/', views.order_details, name='order_details'),
    path('login/', LoginView.as_view(), name="custom_login" ),
    path('signup/', SignupView.as_view(), name="custom_singup" ),
]

      

views.py

from django.contrib.auth import authenticate, login, get_user_model
from django.views.generic import CreateView, FormView
from django.http import HttpResponse
from django.shortcuts import render,redirect
from django.utils.http import is_safe_url


from .forms import LoginForm, RegisterForm, GuestForm
from .models import User


def guest_register_view(request):
    form = GuestForm(request.POST or None)
    context = {
        "form": form
    }
    next_ = request.GET.get('next')
    next_post = request.POST.get('next')
    redirect_path = next_ or next_post or None
    if form.is_valid():
        email       = form.cleaned_data.get("email")
        new_guest_email = GuestEmail.objects.create(email=email)
        request.session['guest_email_id'] = new_guest_email.id
        if is_safe_url(redirect_path, request.get_host()):
            return redirect(redirect_path)
        else:
            return redirect("/register/")
    return redirect("/register/")



class LoginView(FormView):
    form_class = LoginForm
    success_url = '/'
    template_name = 'accounts/login.html'

    def form_valid(self, form):
        request = self.request
        next_ = request.GET.get('next')
        next_post = request.POST.get('next')
        redirect_path = next_ or next_post or None
        email  = form.cleaned_data.get("email")
        password  = form.cleaned_data.get("password")
        user = authenticate(request, username=email, password=password)
        if user is not None:
            login(request, user)
            try:
                del request.session['guest_email_id']
            except:
                pass
            if is_safe_url(redirect_path, request.get_host()):
                return redirect(redirect_path)
            else:
                return redirect("/")
        return super(LoginView, self).form_invalid(form)


class RegisterView(CreateView):
    form_class = RegisterForm
    template_name = 'accounts/register.html'
    success_url = '/login/'


      

forms.py

from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import ReadOnlyPasswordHashField


User = get_user_model()


class UserAdminCreationForm(forms.ModelForm):
    """A form for creating new users. Includes all the required
    fields, plus a repeated password."""
    password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)

    class Meta:
        model = User
        fields = ('username', 'email',) #'username',)

    def clean_password2(self):
        # Check that the two password entries match
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")
        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("Passwords don't match")
        return password2

    def save(self, commit=True):
        # Save the provided password in hashed format
        user = super(UserAdminCreationForm, self).save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()
        return user



class UserAdminChangeForm(forms.ModelForm):
    """A form for updating users. Includes all the fields on
    the user, but replaces the password field with admin's
    password hash display field.
    """
    password = ReadOnlyPasswordHashField()

    class Meta:
        model = User
        fields = ('username', 'email', 'password', 'is_active', 'is_staff')

    def clean_password(self):
        # Regardless of what the user provides, return the initial value.
        # This is done here, rather than on the field, because the
        # field does not have access to the initial value
        return self.initial["password"]



class GuestForm(forms.Form):
    email    = forms.EmailField()


class LoginForm(forms.Form):
    email    = forms.EmailField(label='Email')
    password = forms.CharField(widget=forms.PasswordInput)




class RegisterForm(forms.ModelForm):
    """A form for creating new users. Includes all the required
    fields, plus a repeated password."""
    password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)

    class Meta:
        model = User
        fields = ('username', 'email',) #'username',)

    def clean_password2(self):
        # Check that the two password entries match
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")
        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("Passwords don't match")
        return password2

    def save(self, commit=True):
        # Save the provided password in hashed format
        user = super(RegisterForm, self).save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        # user.active = False # send confirmation email
        if commit:
            user.save()
        return user



      
$ cd ../../
$ nano manage.py

ADD dev

$ cd cartturbo
$ nano wsgi.py

ADD dev

Setup allauth

pip install django-allauth
DEFAULT_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
]
THIRD_PARTY_APPS = [
  'allauth',
]
LOCAL_APPS = [
  'apps.accounts',
]

INSTALLED_APPS = DEFAULT_APPS + LOCAL_APPS + THIRD_PARTY_APPS

SITE_ID = 1
AUTHENTICATION_BACKENDS = [
    # Needed to login by username in Django admin, regardless of `allauth`
    'django.contrib.auth.backends.ModelBackend',

    # `allauth` specific authentication methods, such as login by e-mail
    'allauth.account.auth_backends.AuthenticationBackend',
]

import os

STATIC_URL = '/static/'
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
STATIC_ROOT = os.path.join(BASE_DIR, 'static_root')
urlpatterns = [
    ...
    path('accounts/', include('allauth.urls')),
    ...
]

Add apps to front of accounts inside the apps.py.

from django.apps import AppConfig
class AccountsConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'apps.accounts'

Add to settings

AUTH_USER_MODEL = 'accounts.User'
$ python3 manage.py makemigrations accounts
$ python3 manage.py migrate accounts
$ python3 manage.py migrate 
$ python3 manage.py collectstatic
$ python3 manage.py createsuperuser

CartTurbo

And a password and the password should be a quote, remove all the spaces and change out any letters you can with characters like s becomes $ t can become 7 and so on.

1\/\/1ll|_|77er|)4rk$4y1ing$Fr(

$ python3 manage.py runserver
cd templates/account
touch {login,logout,password_change,password_reset,signup}.html
cd ../ ../

login.html

signup.html

logout.html

password_rest.html

Setting Shopping Cart

 $ cd ../../
 $ cd apps
 $ django-admin startapp catalog
 $ cd ../
 $ cd settings

base.py

 
DEFAULT_APPS = [

'django.contrib.admin',

'django.contrib.auth',

'django.contrib.contenttypes',

'django.contrib.sessions',

'django.contrib.messages',

'django.contrib.staticfiles',

]

THIRD_PARTY_APPS = [

'',

]

LOCAL_APPS = [

'apps.accounts',

'apps.catalog',

]

INSTALLED_APPS = DEFAULT_APPS + LOCAL_APPS + THIRD_PARTY_APPS

Change Templates

 
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.i18n',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

$ python3 -m pip install --upgrade Pillow

$ pip install django-widget-tweaks
 
DEFAULT_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.humanize',
]


THIRD_PARTY_APPS = [
    'allauth',
    'widget_tweaks',
]


LOCAL_APPS = [
    'apps.accounts',
    'apps.catalog',
  
]

 

Catalog apps.py

from django.apps import AppConfig


class CatalogConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'apps.catalog'

Catalog Models

Models.py

from django.db import models
from django.conf import settings
from django.urls import reverse
from django.utils.text import slugify

User = settings.AUTH_USER_MODEL

class Category(models.Model):
    title                       = models.CharField(max_length=255)
    url_slug                    = models.CharField(max_length=255)
    thumbnail                   = models.FileField(null=True, blank=True)
    description                 = models.TextField(null=True, blank=True)
    created_at                  = models.DateTimeField(auto_now_add=True)
    is_active                   = models.BooleanField(default=True)

    class Meta:
        ordering                = ('title',)
        unique_together         = ('url_slug', 'title',)

    def get_absolute_url(self):
        return reverse("catalog:category_list")

    def __str__(self):
        return self.title

class SubCategory(models.Model):
    category                    = models.ForeignKey(Category, on_delete=models.CASCADE)
    title                       = models.CharField(max_length=255)
    url_slug                    = models.CharField(max_length=255)
    thumbnail                   = models.FileField()
    description                 = models.TextField()
    created_at                      = models.DateTimeField(auto_now_add=True)
    is_active                       = models.BooleanField(default=True)

    def get_absolute_url(self):
        return reverse("catalog:sub_category_list")

    def __str__(self):
        return self.title

class Product(models.Model):
    name                            = models.CharField(max_length=255)
    featured                        = models.BooleanField(default=False)

    def __str__(self):
        return self.name

class Brand(models.Model):
    name                            = models.CharField(max_length=255)

    def __str__(self):
        return self.name

class RidingStyle(models.Model):
    name                            = models.CharField(max_length=255)

    def __str__(self):
        return self.name

class ProductCategory(models.Model):
    name                            = models.CharField(max_length=255)

    def __str__(self):
        return self.name

class MotorcyclePart(models.Model):
    product                         = models.OneToOneField(Product, on_delete=models.CASCADE)
    slug                            = models.SlugField(max_length=255,default='')
    featured                        = models.BooleanField(default=False)
    bestseller                      = models.BooleanField(default=False)
    available                       = models.BooleanField(default=True)
    brand                           = models.ForeignKey(Brand, on_delete=models.CASCADE)
    part_number                     = models.CharField(max_length=100, null=True, blank=True, verbose_name='Número de parte')
    price                           = models.DecimalField(max_digits=9, decimal_places=3, default=00.000)
    discount_price                  = models.DecimalField(max_digits=9, decimal_places=3, blank=True, null=True)
    image                           = models.ImageField(upload_to='images/hero/img', null=True, blank=True)
    part_qty_in_stock               = models.PositiveIntegerField(null=True, blank=True, verbose_name='Cantidad en inventario')
    part_min_qty                    = models.PositiveIntegerField(null=True, blank=True, verbose_name='Cantidad Mínima')
    part_compatible                 = models.CharField(max_length=100, null=True, blank=True, verbose_name='Dónde utilizar')
    riding_style                    = models.ManyToManyField(RidingStyle)
    category                        = models.ManyToManyField(ProductCategory)
    timestamp                       = models.DateTimeField(auto_now_add=True, editable=True, blank=True, null=True)

    def __str__(self):
        return self.product.name

    class Meta:
        ordering                    = ['part_number']
        verbose_name                = 'product'
        verbose_name_plural         = 'products'
        unique_together             = ('slug', 'product',)

    def save(self, *args, **kwargs):
        self.slug                   = slugify(self.name)
        super().save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse("catalog:motorcyclepart_detail", kwargs={"pk": self.pk})


class Car(models.Model):
    third_row_seat                  = models.BooleanField()
    adjustable_pedals               = models.BooleanField()
    alloy_wheels                    = models.BooleanField()
    android_auto                    = models.BooleanField()
    apple_carplay                   = models.BooleanField()
    auto_high_beam_headlights       = models.BooleanField()
    automatic_cruise_control        = models.BooleanField()
    body_style                      = models.CharField(max_length=100)
    make                            = models.CharField(max_length=100)
    certified                       = models.BooleanField()
    conditions                      = models.CharField(max_length=100)
    distance_pacing_cruise_control = models.BooleanField()
    drive_line = models.CharField(max_length=100)
    dvd_entertainment = models.BooleanField()
    emergency_communication_system = models.BooleanField()
    exterior_color = models.CharField(max_length=100)
    features = models.CharField(max_length=100)
    fog_lights = models.BooleanField()
    forward_collision_warning = models.BooleanField()
    fuel_type = models.CharField(max_length=100)
    hands_free_liftgate = models.BooleanField()
    heads_up_display = models.BooleanField()
    heated_cooled_seats = models.BooleanField()
    heated_steering_wheel = models.BooleanField()
    interior_color = models.CharField(max_length=100)
    keyless_entry = models.BooleanField()
    lane_departure_warning = models.BooleanField()
    leather_seats = models.BooleanField()
    make = models.CharField(max_length=100)  # Duplicate field, removed
    navigation_system = models.BooleanField()
    parking_sensors = models.BooleanField()
    power_door_locks = models.BooleanField()
    power_mirrors = models.BooleanField()
    power_seats = models.BooleanField()
    power_windows = models.BooleanField()
    premium_audio = models.BooleanField()
    rear_view_camera = models.BooleanField()
    remote_start = models.BooleanField()
    satellite_radio = models.BooleanField()
    sunroof_moonroof = models.BooleanField()
    price                           = models.DecimalField(max_digits=9, decimal_places=3, default=00.000)
    location                        = models.CharField(max_length=100)
    vin = models.CharField(max_length=100)
    color = models.CharField(max_length=100)

    def __str__(self):
        return f"{self.make} - {self.vin}"

    class Meta:
        verbose_name_plural = 'Cars'

    def get_absolute_url(self):
        return reverse("catalog:car_detail", kwargs={"pk": self.pk})

class Motorcycle(models.Model):
    year                            = models.IntegerField()
    manufacturer = models.CharField(max_length=100)
    model_name = models.CharField(max_length=100)
    trim_name = models.CharField(max_length=100)
    generic_type = models.CharField(max_length=100)
    engine_type = models.CharField(max_length=100)
    bore = models.CharField(max_length=100)
    stroke = models.CharField(max_length=100)
    engine_power = models.CharField(max_length=100)
    engine_torque = models.CharField(max_length=100)
    fuel_type = models.CharField(max_length=100)
    transmission = models.CharField(max_length=100)
    front_suspension = models.CharField(max_length=100)
    front_travel = models.CharField(max_length=100)
    front_shocks = models.CharField(max_length=100)
    rear_suspension = models.CharField(max_length=100)
    rear_travel = models.CharField(max_length=100)
    rear_shocks = models.CharField(max_length=100)
    front_braking_system = models.CharField(max_length=100)
    rear_braking_system = models.CharField(max_length=100)
    parking_brake = models.CharField(max_length=100)
    electronic_brake_distribution = models.CharField(max_length=100)
    front_tire = models.CharField(max_length=100)
    rear_tire = models.CharField(max_length=100)
    front_wheel = models.CharField(max_length=100)
    rear_wheel = models.CharField(max_length=100)
    overall_vehicle_size = models.CharField(max_length=100)
    wheelbase = models.CharField(max_length=100)
    seat_height = models.CharField(max_length=100)
    ground_clearance = models.CharField(max_length=100)
    seating_capacity = models.CharField(max_length=100)
    storage_capacity = models.CharField(max_length=100)
    fuel_capacity = models.CharField(max_length=100)
    cargo_capacity = models.CharField(max_length=100)
    dry_weight = models.CharField(max_length=100)
    safety_features = models.CharField(max_length=100)
    main_functions = models.CharField(max_length=100)
    instrumentation = models.CharField(max_length=100)
    warranty = models.CharField(max_length=100)
    price                           = models.DecimalField(max_digits=9, decimal_places=3, default=00.000)
    location = models.CharField(max_length=100)
    condition = models.CharField(max_length=100)
    stock_number = models.CharField(max_length=100)
    vin = models.CharField(max_length=100)
    color = models.CharField(max_length=100)

    def __str__(self):
        return f"{self.manufacturer} {self.model_name}"

    class Meta:
        verbose_name_plural = 'Motorcycles'

    def get_absolute_url(self):
        return reverse("catalog:motorcycle_detail", kwargs={"pk": self.pk})


class Accessory(models.Model):
    name = models.CharField(max_length=100)
    category = models.CharField(max_length=100)
    description = models.TextField()
    price = models.DecimalField(max_digits=10, decimal_places=2)
    location = models.CharField(max_length=100)
    condition = models.CharField(max_length=100)
    stock_number = models.CharField(max_length=100)
    vin = models.CharField(max_length=100)
    color = models.CharField(max_length=100)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = 'Accessories'

    def get_absolute_url(self):
        return reverse("catalog:accessory_detail", kwargs={"pk": self.pk})




$ python3 manage.py makemigrations

 

$ python3 manage.py migrate

 

$ admin.py

 

from django.contrib import admin
from .models import Motorcycle, Product, Brand, RidingStyle, ProductCategory, MotorcyclePart


@admin.register(Motorcycle)
class MotorcycleAdmin(admin.ModelAdmin):
    search_fields = ['manufacturer', 'model_name']
    list_display = ('manufacturer', 'model_name', 'price') 
    list_filter = ('manufacturer',)
    fieldsets = (
        ('General Information', {
            'fields': ('manufacturer', 'model_name', 'price'),
        }),
        ('Additional Information', {
            'fields': ('color', 'description'),
        }),
    )

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ['name', 'featured']
    
@admin.register(MotorcyclePart)
class MotorcyclePartAdmin(admin.ModelAdmin):
    list_display = ['product', 'brand', 'part_number', 'price', 'bestseller', 'available']
    list_filter = ['brand', 'bestseller', 'available']
    fieldsets = (
        ('General Information', {
            'fields': ('product', 'brand', 'part_number', 'price'),
        }),
        ('Additional Information', {
            'fields': ('bestseller', 'available', 'image', 'part_qty_in_stock', 'part_min_qty', 'part_compatible'),
        }),
        ('Relationships', {
            'fields': ('riding_style', 'category'),
        }),
    )

    class Meta:
        model = MotorcyclePart  # Add this line
        ordering = ['part_number']

admin.site.register(Brand)
admin.site.register(RidingStyle)
admin.site.register(ProductCategory)

views.py

from django.shortcuts import render, get_object_or_404, redirect
from django.views.generic import ListView, DetailView
from .models import Motorcycle, Product, Car, Accessory,MotorcyclePart,SubCategory,Category
from .forms import MotorcycleForm, ProductForm

class MotorcycleListView(ListView):
    model = Motorcycle
    template_name = 'catalog/motorcycle_list.html'
    context_object_name = 'motorcycles'

class MotorcycleDetailView(DetailView):
    model = Motorcycle
    template_name = 'catalog/motorcycle_detail.html'
    context_object_name = 'motorcycle'

class ProductListView(ListView):
    model = Product
    template_name = 'catalog/product_list.html'
    context_object_name = 'products'

class ProductDetailView(DetailView):
    model = Product
    template_name = 'catalog/product_detail.html'
    context_object_name = 'product'

def motorcycle_create(request):
    if request.method == 'POST':
        form = MotorcycleForm(request.POST)
        if form.is_valid():
            form.save()
            # Redirect to the motorcycle list page or any other desired page
            return redirect('catalog:motorcycle_list')
    else:
        form = MotorcycleForm()
    
    context = {'form': form}
    return render(request, 'catalog/motorcycle_create.html', context)

def motorcycle_update(request, pk):
    motorcycle = get_object_or_404(Motorcycle, pk=pk)
    if request.method == 'POST':
        form = MotorcycleForm(request.POST, instance=motorcycle)
        if form.is_valid():
            form.save()
            return redirect('catalog:motorcycle_detail', pk=pk)
    else:
        form = MotorcycleForm(instance=motorcycle)
    return render(request, 'catalog/motorcycle_update.html', {'form': form})

def product_create(request):
    if request.method == 'POST':
        form = ProductForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('catalog:product_list')
    else:
        form = ProductForm()
    return render(request, 'catalog/product_create.html', {'form': form})

def product_update(request, pk):
    product = get_object_or_404(Product, pk=pk)
    if request.method == 'POST':
        form = ProductForm(request.POST, instance=product)
        if form.is_valid():
            form.save()
            return redirect('catalog:product_detail', pk=pk)
    else:
        form = ProductForm(instance=product)
    return render(request, 'catalog/product_update.html', {'form': form})

def home(request):
    featured = MotorcyclePart.objects.filter(featured=True)
    bestseller = MotorcyclePart.objects.filter(bestseller=True)
    latest = MotorcyclePart.objects.order_by('-timestamp')[0:10]
    products = MotorcyclePart.objects.filter(available=True)
    context = {
            'object_list': featured,
            'latest': latest,
            'bestseller': bestseller,
            'products': products
        }    
    return render(request, 'catalog/index.html', context)


def search_parts(request):
    if request.method == 'GET':
        keyword = request.GET.get('keyword', '')
        min_price = request.GET.get('min_price')
        max_price = request.GET.get('max_price')
        # Add more filters as per your requirements
        
        motorcycles = Motorcycle.objects.filter(
            model_name__icontains=keyword,
            sale_price__range=(min_price, max_price),
            # Add more filters for motorcycles
        )
        
        cars = Car.objects.filter(
            make__icontains=keyword,
            sale_price__range=(min_price, max_price),
            # Add more filters for cars
        )
        
        accessories = Accessory.objects.filter(
            name__icontains=keyword,
            price__range=(min_price, max_price),
            # Add more filters for accessories
        )
        
        context = {
            'motorcycles': motorcycles,
            'cars': cars,
            'accessories': accessories,
        }
        
        return render(request, 'search_results.html', context)

urls.py

from django.urls import path
from . import views

app_name = 'catalog'

urlpatterns = [
    path('', views.home, name='home'),
    path('motorcycles/', views.MotorcycleListView.as_view(), name='motorcycle_list'),
    path('motorcycles/<int:pk>/', views.MotorcycleDetailView.as_view(), name='motorcycle_detail'),
    path('products/', views.ProductListView.as_view(), name='product_list'),
    path('products/<int:pk>/', views.ProductDetailView.as_view(), name='product_detail'),
    path('motorcycles/create/', views.motorcycle_create, name='motorcycle_create'),
    path('motorcycles/<int:pk>/update/', views.motorcycle_update, name='motorcycle_update'),
    path('products/create/', views.product_create, name='product_create'),
    path('products<int:pk>/update/', views.product_update, name='product_update'),
]
 

forms.py

from django import forms
from django.contrib import admin
from .models import Motorcycle, Product

class MotorcycleForm(forms.ModelForm):
    class Meta:
        model = Motorcycle
        fields = '__all__'

class ProductForm(forms.ModelForm):
    class Meta:
        model = Product
        fields = '__all__'

 

urlpatterns = [
    ...
    path('catalog/', include('apps.catalog.urls')),
    ...
]
cd templates/catalog
touch {index,motorcycle_list,motorcycle_detail,product_list,product_detail}.html
cd ..
cd tags
touch {sidebar,header,footer}.html
cd ../../

Tags/Header.html

Tags/aside.html

Root.HTML

Footer.html

$ python3 manage.py makemigrations accounts
$ python3 manage.py migrate accounts
$ python3 manage.py migrate 
$ python3 manage.py collectstatic