# Django Models

Django models define how the data is structured in the database. Basic model structure will include the following:

# Typical Imports

Take what you need 🎉 💯

from django.db import models
from tinymce.models import HTMLField
from django.template.defaultfilters import slugify
from django.contrib.auth.models import User
from django.utils import timezone
from uuid import uuid4
from django.conf import settings
from django.urls import reverse
import json
import os

#Import models from other apps
from .xxxxx import *

# Basic Model Structure

In this example, we use Contact as an example

class Contact(models.Model):
    #Standard Variables
    title = models.CharField(null=True, blank=True, max_length=200)
    #### ADD OTHER VARIABLES HERE

    #Related Variables
    user = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE)

    #Utility Variable
    uniqueId = models.CharField(null=True, blank=True, max_length=100)
    slug = models.SlugField(max_length=500, unique=True, blank=True, null=True)
    date_created = models.DateTimeField(blank=True, null=True)
    last_updated = models.DateTimeField(blank=True, null=True)

    def __str__(self):
        return '{}{}'.format(self.title, self.uniqueId)


    def get_absolute_url(self):
        return reverse('contact-detail', kwargs={'slug': self.slug})


    def save(self, *args, **kwargs):
        if self.date_created is None:
            self.date_created = timezone.localtime(timezone.now())
        if self.uniqueId is None:
            self.uniqueId = str(uuid4()).split('-')[4]
            self.slug = slugify('{}{}'.format(self.title, self.uniqueId))


        self.slug = slugify('{}{}'.format(self.title, self.uniqueId))
        self.last_updated = timezone.localtime(timezone.now())
        super(Contact, self).save(*args, **kwargs)

# Model Elements

These are the typical elements that go in to a model

#Character field
name = models.CharField(null=True, blank=True, max_length=200)

#Slug field
slug = models.SlugField(max_length=500, unique=True, blank=True, null=True)

#Datetime field
date = models.DateTimeField(blank=True, null=True)

#Text area field
description = models.TextField(null=True, blank=True)

#Foreignkey SET NULL
user = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL)

#Foreign key CASCADE
user = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE)

#Multiple choice field
OPTIONS = [
('option1', 'option1'),
('option2', 'option2'),
('option3', 'option3'),
]
selection = models.CharField(choices=OPTIONS, null=True, blank=True, max_length=100)

#Boolean field
check = models.BooleanField(default=False)

#Image Field
image = models.ImageField(default='default.jpg', upload_to='profile_imgs')

#Resized Image Field
image = ResizedImageField(size=[400, 400], crop=['middle', 'center'], default='default.jpg', upload_to='profile_imgs')

#File Uploading to dynamic directory - including File Renaming to UUID name
def the_upload_path(self, filename):
        ext = filename.split('.')[-1]
        filename = '{}.{}'.format(str(uuid4()).split('-')[4], ext)
        return os.path.join('{}/directory'.format(self.uniqueId), filename)
file = models.FileField(upload_to=the_upload_path, null=True, blank=True,)

#File uploading to fixed directory
file = models.FileField(upload_to='upload_directory', null=True, blank=True,)

#OneToOneField with RELATED Field
user = models.OneToOneField(User, on_delete = models.CASCADE, related_name='related_employee')

#Integer field
days = models.IntegerField()

#Float Field
amount = models.FloatField()

# Documentation Handling

Validate Document size in the model

def validate_doc_size(value):
    filesize= value.size
    if filesize > 5242880:
        raise ValidationError("The maximum document size that can be uploaded is 5MB")
    else:
        return value

document = models.FileField(upload_to=the_upload_path, null=True, blank=True, validators=[validate_doc_size])

Make sure documents are deleted from storage when a model instance is deleted, or updated.


from django.db.models.signals import post_delete, pre_save
from django.dispatch import receiver

""" Whenever ANY model is deleted, if it has a file field on it, delete the associated file too"""
@receiver(post_delete)
def delete_files_when_row_deleted_from_db(sender, instance, **kwargs):
    for field in sender._meta.concrete_fields:
        if isinstance(field,models.FileField):
            instance_file_field = getattr(instance,field.name)
            delete_file_if_unused(sender,instance,field,instance_file_field)

""" Delete the file if something else get uploaded in its place"""
@receiver(pre_save)
def delete_files_when_file_changed(sender,instance, **kwargs):
    # Don't run on initial save
    if not instance.pk:
        return
    for field in sender._meta.concrete_fields:
        if isinstance(field,models.FileField):
            #its got a file field. Let's see if it changed
            try:
                instance_in_db = sender.objects.get(pk=instance.pk)
            except sender.DoesNotExist:
                # We are probably in a transaction and the PK is just temporary
                # Don't worry about deleting attachments if they aren't actually saved yet.
                return
            instance_in_db_file_field = getattr(instance_in_db,field.name)
            instance_file_field = getattr(instance,field.name)
            if instance_in_db_file_field.name != instance_file_field.name:
                delete_file_if_unused(sender,instance,field,instance_in_db_file_field)

""" Only delete the file if no other instances of that model are using it"""
def delete_file_if_unused(model,instance,field,instance_file_field):
    dynamic_field = {}
    dynamic_field[field.name] = instance_file_field.name
    other_refs_exist = model.objects.filter(**dynamic_field).exclude(pk=instance.pk).exists()
    if not other_refs_exist:
        instance_file_field.delete(False)