STAY INFORMED
following content serves as a personal note and may lack complete accuracy or certainty.

Minimal-Mistakes instruction
Useful vscode Shortcut Keys
Unix Commands
npm Commands
Vim Commands
Git Note
Useful Figma Shortcut Keys

4 minute read

Validators

Django basically offers password validator, but you can also set your custom validation.

In Model.py

You can simply add error_message(validator) with dictionary format.

# models.py
from django.db import models
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    nickname = models.CharField(
        max_length=15,
        unique=True,
        null=True,
        error_messages={"unique": "A user is already registered with this nickname."})

    def __str__(self):
        return self.email

Create validators.py

Or create validators.py and add validator functions.

import string
from django.core.exceptions import ValidationError

def contains_special_character(value):
    for char in value:
        if char in string.punctuation:
            return True
    return False



def validate_no_special_characters(value):
    if contains_special_character(value):
        raise ValidationError("special character cannot be included")

and apply the functions to models.

# models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
from .validators import validate_no_special_characters

class User(AbstractUser):
    nickname = models.CharField(
        max_length=15, 
        unique=True, 
        null=True,
        validators=[validate_no_special_characters],
        error_messages={"unique": "A user is already registered with this nickname."})

    def __str__(self):
        return self.email

Password Validator

There is a default password validator like I said before, but you can use your custom password validator.

Create a password validator class,

# validators.py
import string
from django.core.exceptions import ValidationError

def contains_uppercase_letter(value):
    for char in value:
        if char.isupper():
            return True
    return False

def contains_lowercase_letter(value):
    for char in value:
        if char.islower():
            return True
    return False

def contains_number(value):
    for char in value:
        if char.isdigit():
            return True
    return False


def contains_special_character(value):
    for char in value:
        if char in string.punctuation:
            return True
    return False


class CustomPasswordValidator:
    def validate(self, password, user=None):
        if (
            len(password) < 8 or
            not contains_uppercase_letter(password) or
            not contains_lowercase_letter(password) or
            not contains_number(password) or 
            not contains_special_character(password)
        ):
            raise ValidationError("Your password must contain at least 8 characters.")
        
    def get_help_text(self):
        return "Your password must contain at least 8 characters."

and you will see default password validator is registered.

# settings.py
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',
    },
]

Remove the code and add

# settings.py
AUTH_PASSWORD_VALIDATORS = [
    {
        "NAME": "ecommerce.validators.CustomPasswordValidator",
    },
]

des1

There is a problem. When submitted with inappropriate conditions, password field is empty. You just need one line in settings.py to fix this issue, but this is less secure.

# settings.py
ACCOUNT_PASSWORD_INPUT_RENDER_VALUE = True # Shows password input on form re-render; less secure.

des2

Create Models

I need User, Product, Order, OrderItem, Cart, CartItem, Review models. I already made User model, but need to add some more(first name, last name, address, order history, createdAt).

User

There is a third-party module to handle country, province part.

pip install django-countries

and now you can use the module.

# models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
from django_countries.fields import CountryField
from .validators import validate_no_special_characters

class User(AbstractUser):
    firstName = models.CharField(max_length=15)
    lastName = models.CharField(max_length=15)
    nickname = models.CharField(
        max_length=15, 
        unique=True, 
        null=True,
        validators=[validate_no_special_characters],
        error_messages={
            "unique": "A user is already registered with this nickname."
        }
    )
    address = models.CharField(max_length=15)
    city = models.CharField(max_length=15)
    country = CountryField(blank_label='(select country)')
    province = models.CharField(max_length=2)
    postalCode = models.CharField(max_length=6)
    history = models.TextField(blank=True, null=True)
    dt_created = models.DateTimeField(verbose_name="Date Created", auto_now_add=True)

    def __str__(self):
        return self.email

Also need to apply to form.

# forms.py
from django import forms
from django_countries.fields import CountryField
from django_countries.widgets import CountrySelectWidget
from localflavor.ca.forms import CAPostalCodeField, CAProvinceField
from .models import User


class SignupForm(forms.ModelForm):
    country = CountryField(blank_label='(select country)').formfield(
        widget=CountrySelectWidget(attrs={'class': 'form-control country-select-widget'}) # this has .form-control-country-select-widget
    )
    class Meta:
        model = User
        fields = ["firstName", "lastName", "nickname", "address", "city", "country", "province", "postalCode"]

    def signup(self, request, user):
        user.nickname = self.cleaned_data["nickname"]
        user.firstName = self.cleaned_data["firstName"]
        user.lastName = self.cleaned_data["lastName"]
        user.address = self.cleaned_data["address"]
        user.city = self.cleaned_data["city"]
        user.country = self.cleaned_data["country"]
        user.province = self.cleaned_data["province"]
        user.postalCode = self.cleaned_data["postalCode"]
        user.save()

I created signup.html and applied css. If you want to your custom signup page, the file name should be signup.html and in INSTALLED_APPS in settings.py, the index of your project should be forward than ‘allauth’.

Rest


class Item(models.Model):
    name = models.CharField(max_length=15)
    category = models.CharField(max_length=15)
    available = models.BooleanField(default=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    description = models.TextField(blank=True)
    image = models.ImageField(upload_to='items/images/', blank=True, null=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='items', null=True, blank=True)

    def __str__(self):
        return self.name


class Order(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='orders')
    firstName = models.CharField(max_length=15)
    lastName = models.CharField(max_length=15)
    email = models.EmailField()
    address = models.TextField()
    postal_code = models.CharField(max_length=20)
    city = models.CharField(max_length=15)
    createdAt = models.DateTimeField(auto_now_add=True)
    updatedAt = models.DateTimeField(auto_now=True)
    is_paid = models.BooleanField(default=False)
    paymentId = models.CharField(max_length=15, blank=True, null=True)

    def __str__(self):
        return f'Order {self.id} by {self.first_name} {self.last_name}'

class OrderItem(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='order_items')
    item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='order_items')
    price = models.DecimalField(max_digits=10, decimal_places=2)
    quantity = models.PositiveIntegerField()

    def __str__(self):
        return f'{self.item.name} (x{self.quantity})'


class Cart(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='carts')
    createdAt = models.DateTimeField(auto_now_add=True)
    updatedAt = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f'Cart for {self.user.email}'


class CartItem(models.Model):
    cart = models.ForeignKey(Cart, on_delete=models.CASCADE, related_name='cart_items')
    item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='cart_items')
    quantity = models.PositiveIntegerField()
    price = models.DecimalField(max_digits=10, decimal_places=2)

    def __str__(self):
        return f'{self.item.name} (x{self.quantity})'

class Review(models.Model):
    rate = models.PositiveIntegerField()
    comment = models.TextField()
    createdAt = models.DateTimeField(auto_now_add=True)
    item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='reviews')
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reviews')

    def __str__(self):
        return f'Review by {self.user.email} for {self.item.name}'

Image Field

For image field, need to install ‘Pillow’.

pip install Pillow

To upload dynamic image, need to set several things. I createad media/item_pics in root directory and add this two line in settings.py.

# settings.py
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = "/uploads/"

After, go to urls.py in project directory and

from django.conf import settings
from django.conf.urls.static import static

...

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Now if you register an item, an image will be saved in media/items/

des3 des4