many-to-many relationship in SQLAlchemy

In SQLAlchemy, a many-to-many relationship is represented using an association table. This table contains foreign keys that reference the primary keys of the two tables that are involved in the many-to-many relationship. Here’s a step-by-step guide to setting up a many-to-many relationship in SQLAlchemy:

  1. Define the association table: This table contains only foreign keys and optionally other columns.
  2. Define the models: Define the two tables involved in the many-to-many relationship.
  3. Set up the relationship: Use SQLAlchemy’s relationship function to establish the many-to-many relationship.

Here is an example with two models: Student and Course, which have a many-to-many relationship through an enrollment association table.

Step 1: Define the Association Table

from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

enrollment = Table('enrollment', Base.metadata,
    Column('student_id', Integer, ForeignKey('students.id'), primary_key=True),
    Column('course_id', Integer, ForeignKey('courses.id'), primary_key=True)
)

Step 2: Define the Models

from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship

class Student(Base):
    __tablename__ = 'students'
    
    id = Column(Integer, primary_key=True)
    name = Column(String)
    
    courses = relationship('Course', secondary=enrollment, back_populates='students')

class Course(Base):
    __tablename__ = 'courses'
    
    id = Column(Integer, primary_key=True)
    title = Column(String)
    
    students = relationship('Student', secondary=enrollment, back_populates='courses')

Step 3: Create the Database and Tables

from sqlalchemy import create_engine

engine = create_engine('sqlite:///school.db')
Base.metadata.create_all(engine)

Step 4: Establishing the Session

from sqlalchemy.orm import sessionmaker

Session = sessionmaker(bind=engine)
session = Session()

Step 5: Adding Data

# Create new students and courses
student1 = Student(name='John Doe')
student2 = Student(name='Jane Smith')
course1 = Course(title='Math 101')
course2 = Course(title='History 101')

# Establish many-to-many relationships
student1.courses.append(course1)
student2.courses.append(course1)
student2.courses.append(course2)

# Add to session and commit
session.add(student1)
session.add(student2)
session.commit()

Step 6: Querying Data

# Querying students enrolled in Math 101
math_students = session.query(Student).join(enrollment).join(Course).filter(Course.title == 'Math 101').all()

for student in math_students:
    print(student.name)

# Querying courses taken by Jane Smith
jane_courses = session.query(Course).join(enrollment).join(Student).filter(Student.name == 'Jane Smith').all()

for course in jane_courses:
    print(course.title)

In this example, enrollment is the association table that links students and courses. The relationship function with the secondary parameter is used to define the many-to-many relationships in both Student and Course classes. The back_populates parameter ensures that the relationship is bidirectional.

This setup allows you to easily query and manage the many-to-many relationship between students and courses.

Change Expiration Dates for access and refresh tokens in Django JWT

To change the expiration dates for access and refresh tokens when using Django Simple JWT, you can configure the settings in your Django project’s settings file (settings.py). Here’s how you can do it:

  1. Install Simple JWT (if not already installed):
    pip install djangorestframework-simplejwt
    
  2. Update settings.py to include Simple JWT settings:
    from datetime import timedelta
    
    SIMPLE_JWT = {
        'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),  # Change this to your desired lifetime
        'REFRESH_TOKEN_LIFETIME': timedelta(days=1),  # Change this to your desired lifetime
        'ROTATE_REFRESH_TOKENS': False,
        'BLACKLIST_AFTER_ROTATION': True,
        'UPDATE_LAST_LOGIN': False,
    
        'ALGORITHM': 'HS256',
        'SIGNING_KEY': SECRET_KEY,
        'VERIFYING_KEY': None,
        'AUDIENCE': None,
        'ISSUER': None,
    
        'AUTH_HEADER_TYPES': ('Bearer',),
        'USER_ID_FIELD': 'id',
        'USER_ID_CLAIM': 'user_id',
        'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
        'TOKEN_TYPE_CLAIM': 'token_type',
    
        'JTI_CLAIM': 'jti',
    
        'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
        'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
        'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
    }
    
  3. Update the REST_FRAMEWORK settings to use Simple JWT as the authentication class:
    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework_simplejwt.authentication.JWTAuthentication',
        ),
    }
    

Example

If you want to set the access token to expire in 15 minutes and the refresh token to expire in 7 days, you would update your settings.py as follows:

from datetime import timedelta

SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
    'ROTATE_REFRESH_TOKENS': False,
    'BLACKLIST_AFTER_ROTATION': True,
    'UPDATE_LAST_LOGIN': False,

    'ALGORITHM': 'HS256',
    'SIGNING_KEY': SECRET_KEY,
    'VERIFYING_KEY': None,
    'AUDIENCE': None,
    'ISSUER': None,

    'AUTH_HEADER_TYPES': ('Bearer',),
    'USER_ID_FIELD': 'id',
    'USER_ID_CLAIM': 'user_id',
    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
    'TOKEN_TYPE_CLAIM': 'token_type',

    'JTI_CLAIM': 'jti',

    'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
    'SLIDING_TOKEN_LIFETIME': timedelta(minutes=15),
    'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=7),
}

These settings will ensure that your access tokens expire after 15 minutes and refresh tokens expire after 7 days. Adjust the timedelta values as needed for your application’s requirements.

Add a custom claim to the JWT in Django

Adding a custom claim to the JSON Web Tokens (JWT) in Django using the django-simple-jwt library involves extending the token creation process to include additional information. Here’s how you can achieve this:

  1. Install the necessary libraries: Make sure you have djangorestframework and djangorestframework-simplejwt installed.
    pip install djangorestframework djangorestframework-simplejwt
    
  2. Update your Django settings: Configure django-simple-jwt in your settings.py file.
    INSTALLED_APPS = [
        ...
        'rest_framework',
        'rest_framework_simplejwt',
    ]
    
    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework_simplejwt.authentication.JWTAuthentication',
        ),
    }
    
  3. Create a custom claims serializer: Extend the TokenObtainPairSerializer to include your custom claim.
    from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
    
    class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
    
        def get_token(self, user):
            token = super().get_token(user)
    
            # Add custom claims
            token['custom_claim'] = 'custom_value'
            
            # Example: Add user's email to the token
            token['email'] = user.email
    
            return token
    
  4. Create a custom view: Use the custom serializer in your view.
    from rest_framework_simplejwt.views import TokenObtainPairView
    from .serializers import MyTokenObtainPairSerializer
    
    class MyTokenObtainPairView(TokenObtainPairView):
        serializer_class = MyTokenObtainPairSerializer
    
  5. Update your URLs: Include the custom view in your URL configuration.
    from django.urls import path
    from .views import MyTokenObtainPairView
    from rest_framework_simplejwt.views import TokenRefreshView
    
    urlpatterns = [
        path('api/token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'),
        path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
    ]
    
  6. Test your custom claim: Now when you obtain a token, it should include your custom claim.

Create a Management Command to add User in Django

If you want to create a user in Django who does not have access to the admin panel, you can use the create_user method instead of createsuperuser. The create_user method creates a regular user account without admin privileges.

You can create a custom management command to add users. Create a new file management/commands/create_user.py in one of your apps (e.g., myapp):

# myapp/management/commands/create_user.py

from django.core.management.base import BaseCommand
from django.contrib.auth import get_user_model

class Command(BaseCommand):
    help = 'Create a regular user'

    def add_arguments(self, parser):
        parser.add_argument('username', type=str)
        parser.add_argument('email', type=str)
        parser.add_argument('password', type=str)

    def handle(self, *args, **kwargs):
        username = kwargs['username']
        email = kwargs['email']
        password = kwargs['password']

        User = get_user_model()
        user = User.objects.create_user(username=username, email=email, password=password)
        user.save()

        self.stdout.write(self.style.SUCCESS(f'User {username} created successfully'))

After creating this file, you can run the management command:

python manage.py create_user username [email protected] password

Implementing JWT (JSON Web Token) authentication in Django

Step 1: Install Necessary Packages

First, you need to install the required packages. For JWT authentication in Django, you can use the djangorestframework-simplejwt package.

pip install djangorestframework djangorestframework-simplejwt

Step 2: Configure Django Settings

Add rest_framework and rest_framework_simplejwt to your INSTALLED_APPS in settings.py.

INSTALLED_APPS = [
    ...
    'rest_framework',
    'rest_framework_simplejwt',
    ...
]

Next, configure the REST framework to use JWT for authentication:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
}

Step 3: Set Up URLs

In your urls.py, include the views for obtaining and refreshing tokens.

from django.urls import path
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)

urlpatterns = [
    ...
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
    ...
]

Step 4: Create Views and Protect Endpoints

Create views and protect your endpoints using the @api_view decorator and the permission_classes attribute.

from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

@api_view(['GET'])
@permission_classes([IsAuthenticated])
def protected_view(request):
    return Response({'message': 'This is a protected view'})

Step 5: Testing

To test your JWT implementation, you can use tools like Postman or CURL to interact with your API. First, obtain a token by making a POST request to /api/token/ with your username and password.

curl -X POST http://localhost:8000/api/token/ -d "username=yourusername&password=yourpassword"

This will return a response containing the access and refresh tokens. Use the access token to access protected endpoints by including it in the Authorization header.

curl -H "Authorization: Bearer <your_access_token>" http://localhost:8000/protected-endpoint/

Example Project Structure

Here is a basic project structure for reference:

myproject/
    manage.py
    myproject/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    myapp/
        __init__.py
        views.py
        models.py
        urls.py

Example settings.py

# settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework_simplejwt',
    'myapp',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
}

# Additional settings...

Example urls.py

# urls.py

from django.contrib import admin
from django.urls import path
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
from myapp.views import protected_view

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
    path('protected/', protected_view, name='protected_view'),
]

How It Works

The TokenObtainPairView and TokenRefreshView views provided by djangorestframework-simplejwt are already implemented and ready to use. You do not need to create additional views for these endpoints. They are automatically generated when you include them in your urls.py.

  • TokenObtainPairView: This view is used to obtain a pair of access and refresh tokens. You POST to this endpoint with user credentials (username and password) to get the tokens.
  • TokenRefreshView: This view is used to refresh the access token. You POST to this endpoint with a valid refresh token to get a new access token.

Example Requests

  1. Obtain Token Pair:
    curl -X POST http://127.0.0.1:8000/api/token/ -d "username=myusername&password=mypassword"
    

    This will return a JSON response with access and refresh tokens.

  2. Refresh Token:
    curl -X POST http://127.0.0.1:8000/api/token/refresh/ -d "refresh=your_refresh_token"
    

    This will return a new access token.

Example Response

  • TokenObtainPairView Response:
    {
        "refresh": "your_refresh_token",
        "access": "your_access_token"
    }
    
  • TokenRefreshView Response:
    {
        "access": "your_new_access_token"
    }
    

With these steps, you can seamlessly integrate JWT authentication into your Django application using djangorestframework-simplejwt. There is no need to create additional views for these token endpoints as they are provided out of the box by the package.

Django REST framework with function based view

Setup

  1. Install Django and Django REST framework:
    pip install django djangorestframework
    
  2. Create a new Django project:
    django-admin startproject myproject
    cd myproject
    
  3. Create a new Django app:
    python manage.py startapp myapp
    
  4. Add the app and DRF to INSTALLED_APPS in myproject/settings.py:
    INSTALLED_APPS = [
        ...
        'rest_framework',
        'myapp',
    ]
    

Models

Let’s define a simple model in myapp/models.py:

from django.db import models

class Item(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField()

    def __str__(self):
        return self.name

Run migrations to create the database table:

python manage.py makemigrations
python manage.py migrate

Serializers

Create a serializer for the Item model in myapp/serializers.py:

from rest_framework import serializers
from .models import Item

class ItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = Item
        fields = '__all__'

Views

Create function-based views in myapp/views.py:

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from .models import Item
from .serializers import ItemSerializer

@api_view(['GET', 'POST'])
def item_list(request):
    if request.method == 'GET':
        items = Item.objects.all()
        serializer = ItemSerializer(items, many=True)
        return Response(serializer.data)
    elif request.method == 'POST':
        serializer = ItemSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET', 'PUT', 'DELETE'])
def item_detail(request, pk):
    try:
        item = Item.objects.get(pk=pk)
    except Item.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = ItemSerializer(item)
        return Response(serializer.data)
    elif request.method == 'PUT':
        serializer = ItemSerializer(item, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    elif request.method == 'DELETE':
        item.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

URLs

Define the API endpoints in myapp/urls.py:

from django.urls import path
from . import views

urlpatterns = [
    path('items/', views.item_list),
    path('items/<int:pk>/', views.item_detail),
]

Include the app’s URLs in the project’s urls.py:

from django.contrib import admin
from django.urls import path, include

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

Testing the API

Run the development server:

python manage.py runserver

You can now test your API using tools like curl, Postman, or a web browser:

  • GET all items: http://127.0.0.1:8000/api/items/
  • POST a new item: http://127.0.0.1:8000/api/items/ (with JSON data)
  • GET a single item: http://127.0.0.1:8000/api/items/<id>/
  • PUT to update an item: http://127.0.0.1:8000/api/items/<id>/ (with JSON data)
  • DELETE an item: http://127.0.0.1:8000/api/items/<id>/

Django PostgreSQL Model Setup

To work with PostgreSQL in a Django project, you need to configure your Django settings and define your models. Here are the steps:

  1. Install PostgreSQL and psycopg2: Make sure PostgreSQL is installed on your system. Then, install the psycopg2 package, which allows Django to interact with PostgreSQL.
    pip install psycopg2-binary
    
  2. Configure Django settings: In your Django project’s settings.py file, configure the database settings to use PostgreSQL.
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': 'your_database_name',
            'USER': 'your_database_user',
            'PASSWORD': 'your_database_password',
            'HOST': 'localhost',  # Set to your PostgreSQL server address
            'PORT': '5432',       # Default PostgreSQL port
        }
    }
    
  3. Define your Django models: Create your models as usual in your Django app’s models.py file. Here is an example:
    from django.db import models
    
    class Product(models.Model):
        name = models.CharField(max_length=100)
        description = models.TextField()
        price = models.DecimalField(max_digits=10, decimal_places=2)
        created_at = models.DateTimeField(auto_now_add=True)
        updated_at = models.DateTimeField(auto_now=True)
    
        def __str__(self):
            return self.name
    
  4. Apply migrations: Run the following commands to create the necessary database tables for your models:
    python manage.py makemigrations
    python manage.py migrate
    
  5. Use the models: You can now use the models in your views, forms, and other parts of your Django project. For example, to create a new product in a view:
    from django.shortcuts import render
    from .models import Product
    
    def create_product(request):
        if request.method == 'POST':
            name = request.POST.get('name')
            description = request.POST.get('description')
            price = request.POST.get('price')
            product = Product(name=name, description=description, price=price)
            product.save()
            return render(request, 'success.html')
        return render(request, 'create_product.html')
    

By following these steps, you can set up and use PostgreSQL with your Django project.

Class-based views in Django

In Django, a class-based view (CBV) provides an alternative to function-based views (FBVs) by allowing you to use classes and inheritance to organize your code. CBVs offer greater flexibility and reusability compared to FBVs. Below, I’ll provide an overview of how to create and use class-based views in Django.

Creating a Class-Based View

  1. Import the necessary modules:
    from django.views import View
    from django.http import HttpResponse
    
  2. Define your class-based view:
    class MyView(View):
        def get(self, request):
            return HttpResponse('Hello, this is a class-based view!')
    
  3. Map the view to a URL in your urls.py:
    from django.urls import path
    from .views import MyView
    
    urlpatterns = [
        path('myview/', MyView.as_view(), name='myview'),
    ]
    

Types of Class-Based Views

Django provides several built-in class-based views for common use cases, such as:

  • TemplateView: Renders a template.
  • ListView: Displays a list of objects.
  • DetailView: Displays a detail page for a single object.
  • CreateView: Handles object creation.
  • UpdateView: Handles object updates.
  • DeleteView: Handles object deletion.

References
https://docs.djangoproject.com/en/5.0/topics/class-based-views/

many-to-many relationship in Django Model

In Django, a many-to-many relationship is used when you need to associate multiple records from one model with multiple records from another model. This relationship is typically implemented using Django’s ManyToManyField.

Here’s an example to illustrate how to set up a many-to-many relationship between two models, Author and Book, where a book can have multiple authors and an author can write multiple books.

Example

  1. Define the Models:
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Book(models.Model):
    title = models.CharField(max_length=200)
    authors = models.ManyToManyField(Author, related_name='books')

    def __str__(self):
        return self.title

In this example:

  • Author model has a single field name to store the author’s name.
  • Book model has a title field to store the book’s title and a ManyToManyField called authors to establish a many-to-many relationship with the Author model.
  1. Create and Apply Migrations:

Run the following commands to create and apply the migrations for your models:

python manage.py makemigrations
python manage.py migrate
  1. Using the Relationship:

You can now create instances of Author and Book and associate them with each other.

# Creating authors
author1 = Author.objects.create(name='Author 1')
author2 = Author.objects.create(name='Author 2')

# Creating a book
book1 = Book.objects.create(title='Book 1')

# Adding authors to the book
book1.authors.add(author1, author2)

# Accessing related objects
authors_of_book1 = book1.authors.all()  # Returns all authors of book1
books_of_author1 = author1.books.all()  # Returns all books written by author1

Additional Options

  • Through Model: Sometimes you may want to store additional information about the relationship. You can do this by specifying a custom through model.
class Authorship(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    book = models.ForeignKey(Book, on_delete=models.CASCADE)
    contribution = models.CharField(max_length=100)  # Example of additional field

class Book(models.Model):
    title = models.CharField(max_length=200)
    authors = models.ManyToManyField(Author, through='Authorship', related_name='books')

In this setup, the Authorship model acts as an intermediary table, allowing you to add extra fields like contribution.

By using the ManyToManyField in Django, you can efficiently manage complex relationships between models, making your data more relational and structured.

Provide Metadata for the Model in Django

In Django, the Meta class is an inner class that provides metadata to the model. This metadata includes options that affect the behavior of the model, such as its database table name, ordering, and permissions. Here is an overview of some commonly used Meta options:

Common Meta Options

  1. db_table:
    • Specifies the name of the database table to use for the model.
    class MyModel(models.Model):
        name = models.CharField(max_length=100)
    
        class Meta:
            db_table = 'my_custom_table'
    
  2. ordering:
    • Specifies the default ordering for the model records when they are retrieved from the database.
    class MyModel(models.Model):
        name = models.CharField(max_length=100)
        created_at = models.DateTimeField(auto_now_add=True)
    
        class Meta:
            ordering = ['created_at']
    
  3. verbose_name:
    • A human-readable name for the model.
    class MyModel(models.Model):
        name = models.CharField(max_length=100)
    
        class Meta:
            verbose_name = 'My Custom Model'
    
  4. verbose_name_plural:
    • The plural name for the model.
    class MyModel(models.Model):
        name = models.CharField(max_length=100)
    
        class Meta:
            verbose_name_plural = 'My Custom Models'
    
  5. unique_together:
    • Ensures that a set of fields must be unique together.
    class MyModel(models.Model):
        first_name = models.CharField(max_length=30)
        last_name = models.CharField(max_length=30)
    
        class Meta:
            unique_together = ('first_name', 'last_name')
    
  6. index_together:
    • Creates a composite index for a set of fields.
    class MyModel(models.Model):
        first_name = models.CharField(max_length=30)
        last_name = models.CharField(max_length=30)
    
        class Meta:
            index_together = ['first_name', 'last_name']
    
  7. permissions:
    • Custom permissions for the model.
    class MyModel(models.Model):
        name = models.CharField(max_length=100)
    
        class Meta:
            permissions = [
                ('can_view', 'Can view the model'),
            ]
    
  8. abstract:
    • If set to True, this model will be an abstract base class.
    class MyBaseModel(models.Model):
        created_at = models.DateTimeField(auto_now_add=True)
    
        class Meta:
            abstract = True
    
    class MyModel(MyBaseModel):
        name = models.CharField(max_length=100)
    

Example of a Django Model with Meta Options

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'product'
        ordering = ['-created_at']
        verbose_name = 'Product'
        verbose_name_plural = 'Products'
        unique_together = ('name', 'price')
        index_together = ['name', 'created_at']
        permissions = [
            ('can_view_product', 'Can view product'),
        ]

In this example, the Product model has several Meta options specified, including custom table name, default ordering, unique constraints, index constraints, and custom permissions.

References
https://docs.djangoproject.com/en/5.0/ref/models/options/