Intro
When you need to provide a specific behavior for several class-based views, it is recommended that you use mixins
.
Using mixins for class-based views
Mixins are a special kind of multiple inheritance for a class. You can use them to provide common discrete functionality that, when added to other mixins
, allows you to define the behavior of a class.
There are two main situations to use mixins:
- You want to provide multiple optional features for a class
- You want to use a particular feature in several classes
Django comes with several mixins that provide additional functionality to your class-based views.
You can learn more about mixins at https://docs.djangoproject.com/en/4.1/topics/class-based-views/mixins/
.
Django by Example
pg: 542
Image Validation in the django
Success
from django.core.exceptions import ValidationError
def validate_image_size(value):
"""
Validates that the uploaded image is at most 2MB.
"""
# Limit file size to 2MB
max_size = 2 * 1024 * 1024
# Check if the uploaded file exceeds the maximum size
if value.size > max_size:
raise ValidationError('The maximum image size allowed is 2MB.')
# Now use this in the class
class Contact(models.Model):
title = models.CharField(max_length=120)
description = models.TextField(null=True, blank=True)
image = models.ImageField(upload_to="contact/", validators=[validate_image_size])
Note:
In this code:
- We define a custom validator function
validate_image_size
that checks if the uploaded image size exceeds 2MB. If it does, it raises aValidationError
. - We then apply this custom validator to the
image
field of the Contact model.
With this setup, when you attempt to upload an image larger than 2MB via the Django admin interface, it will raise a validation error and prevent the image from being saved.
To reuse the image validation code across multiple Django apps, you can create a separate module for your custom validators and import them wherever needed. Here's how you can do it:
First, create a new file named validators.py
(or any name you prefer) inside your Django app directory or within a shared utilities package:
from django.core.exceptions import ValidationError
def validate_image_size(value, max_size=2 * 1024 * 1024):
"""
Validates that the uploaded image is at most the specified size.
"""
if value.size > max_size:
raise ValidationError(f'The maximum image size allowed is {max_size/(1024*1024)}MB.')
from django.core.exceptions import ValidationError
def validate_image_size(value):
"""
Validates that the uploaded image is at most 2MB.
"""
# Limit file size to 2MB
max_size = 2 * 1024 * 1024
# Check if the uploaded file exceeds the maximum size
if value.size > max_size:
raise ValidationError('The maximum image size allowed is 2MB.')
Now, you can import and use this validator function in any of your models across your Django project. For example, to use it in your Contact model and any other models that require image validation:
from django.db import models
from .validators import validate_image_size
class Contact(models.Model):
title = models.CharField(max_length=120)
description = models.TextField(null=True, blank=True)
image = models.ImageField(upload_to="contact/", validators=[validate_image_size])
def __str__(self):
return self.title
And if you have another model in a different app that also requires image size validation, you can import and use the validator in that model as well:
# models.py in another app
from django.db import models
from shared_utils.validators import validate_image_size
class AnotherModel(models.Model):
title = models.CharField(max_length=120)
image = models.ImageField(upload_to="another_model/", validators=[validate_image_size])
def __str__(self):
return self.title
This way, you can reuse the validate_image_size
function across multiple apps within your Django project without duplicating code. It promotes code organization and maintainability.
Creating a shared utilities folder is a good practice to organize reusable code in a Django project. However, the templates
directory is typically reserved for HTML template files used by your Django views, so it's not the appropriate place for Python code.
Instead, you can create a separate directory within your Django project structure to hold shared utilities. Here's a common structure:
project_name/
│
├── project_name/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
│
├── app1/
│ ├── __init__.py
│ ├── models.py
│ ├── views.py
│ └── ...
│
├── app2/
│ ├── __init__.py
│ ├── models.py
│ ├── views.py
│ └── ...
│
└── utils/
├── __init__.py
├── validators.py
└── other_utils.py
In the structure above:
project_name/
is the root directory of your Django project.app1/
andapp2/
are Django apps within your project.utils/
is a directory where you can place shared utility functions and modules.
So, you would create your validators.py
file inside the utils/
directory:
Then, you can import and use these validators wherever needed in your Django project, as shown in the previous example. This structure keeps your project organized and makes it easy to locate and manage shared utility code.
If you have an apps
directory where you put all your Django apps, and you want to import the validators.py
module located in a shared utilities folder from within one of those apps, you can use a relative import.
Here's how you can organize your project structure to achieve this:
project_name/
│
├── apps/
│ ├── app1/
│ │ ├── __init__.py
│ │ ├── models.py
│ │ └── ...
│ ├── app2/
│ │ ├── __init__.py
│ │ ├── models.py
│ │ └── ...
│ └── ...
│
└── utils/
├── __init__.py
└── validators.py
In this structure, your validators.py
file is located in the utils directory alongside the apps
directory, both at the same level within your project.
To import the validators.py
module from within one of your apps, you can use a relative import like this:
# Inside models.py of app1 or app2
from ..utils.validators import validate_image_size
# or
from shared_utils.validators import validate_image_size
This import statement goes up one level (..
) from the current directory (app1
or app2
) to the parent directory, where both utils
and apps
directories are located. Then it imports the validate_image_size
function from the validators.py
module within the utils
directory.
Using relative imports ensures that your code remains portable and independent of the absolute paths within your project structure.
In Python, the __init__.py
file serves as an indicator that the directory should be treated as a package. Although it can be an empty file, it has several important functions:
-
Package Initialization: It initializes the package when it is imported. This means any code you put in
__init__.py
will be executed when the package is imported. -
Namespace Organization: It helps organize the namespace. Without an
__init__.py
file, Python won't recognize the directory as a package, and you won't be able to import modules from it using dot notation. -
Python 3 Namespace Packages: In Python 3,
__init__.py
is not required to create a package; you can have namespace packages without them. However, it's still common practice to include__init__.py
files for compatibility and to support older Python versions. -
Explicit Initialization: It makes package initialization explicit. By including an
__init__.py
file, you can add any necessary initialization code or imports, making it clear that the directory is intended to be a package.
In the context of Django projects, including an __init__.py
file in your utils directory is not strictly required, especially for Django projects using Python 3 and above. However, it's still considered a good practice for consistency and compatibility with older Python versions. It also allows you to extend your utilities package in the future if needed, by adding initialization code or additional modules.
other
Overview
utils
├── __init__.py
├── custom_char_field_widget.py
├── custom_text_field_widget.py
├── image_display_mixins.py
├── image_validators.py
└── permission_mixins.py
Utils Mixins
Success
from django.forms import Textarea
# class CustomTextFieldWidget(Textarea):
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
# self.attrs.update({'style': 'width: 100%;', 'rows': '10'})
class CustomTextFieldWidget(Textarea):
def __init__(self, *args, **kwargs):
# Default to 10 rows if not provided
rows = kwargs.pop('rows', '10')
super().__init__(*args, **kwargs)
self.attrs.update({'style': 'width: 100%;', 'rows': rows})
# CustomTextFieldWidget(rows='5')
from django.utils.safestring import mark_safe
class ImageDisplayMixin:
"""
Mixin class to display image preview in the Django admin.
"""
@staticmethod
def display_image(obj):
if obj.image:
return mark_safe('<img src="{}" width="100" height="100" />'.format(obj.image.url))
else:
return "No Image"
display_image.short_description = 'Image Preview'
from django.core.exceptions import ValidationError
def validate_image_size(value):
"""
Validates that the uploaded image is at most 2MB.
"""
# Limit file size to 2MB
max_size = 2 * 1024 * 1024
# Check if the uploaded file exceeds the maximum size
if value.size > max_size:
raise ValidationError('The maximum image size allowed is 2MB.')
# mixins.py
class IncrementalSerialNumberMixin:
def get_serial_number(self, obj):
# Get the position of the object in the reversed queryset and add 1 to start from 1 instead of 0
queryset = self.model.objects.all().order_by('-pk') # Assuming pk is the primary key
# queryset = self.model.objects.all()
return list(queryset).index(obj) + 1
get_serial_number.short_description = 'S.N.'
Tip
Views Mixins
Success
from django.views.generic.base import ContextMixin
from industries.models import IndustriesItem
from business.models import BusinessItem
class HeaderMixin(ContextMixin):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
industry_items = IndustriesItem.objects.all()
context['industry_items'] = industry_items
business_items = BusinessItem.objects.all()
context['business_items'] = business_items
return context
Usage
Beginner vs
Senior Code
Handling Nullable Fields in Django Models
# Import necessary module
from django.db import models
# Define the model class
class YourModel(models.Model):
# Define the title field with maximum length and nullable option
title = models.CharField(max_length=100, null=True)
# Define a method to return a string representation of the object
def __str__(self):
# Check if title is not None
if self.title is not None:
# Return the title
return self.title
else:
# Return a default string if title is None
return "Untitled"
# Utilize a concise import statement
from django.db import models
# Define the model class with clear and descriptive naming
class YourModel(models.Model):
# Use descriptive variable names and specify field options clearly
title = models.CharField(max_length=100, null=True)
# Implement a straightforward method for string representation
def __str__(self):
# Use a ternary operator for brevity and readability
return self.title if self.title is not None else "Untitled"
In the senior programmer version, you'll notice more concise variable naming, clearer documentation, and the use of a ternary operator for the __str__
method, which improves readability. Additionally, comments are used sparingly, as the code itself is self-explanatory. This version demonstrates a more efficient and professional coding style, reflecting the experience and expertise of a senior developer.