How to Build a Dynamic Photo Collection App With Django

cover
11 Jul 2024

Creating a dynamic photo collection app that spans different industries is an exciting opportunity in web development. These apps, also known as lookbooks, can be used in fields like fashion, real estate, and travel to showcase visual content effectively.

Recently, I developed a comprehensive lookbook using Django and Cloudinary. You can get a thorough understanding of its functionalities by watching a video walkthrough and reading a detailed description. Additionally, we'll highlight some noteworthy features that developers may find intriguing. Finally, you can fork the app from GitHub to explore its capabilities firsthand.

App Description

Here’s what the app offers:

Homepage Navigation

  • All visitors can view all lookbooks on the homepage.
  • Filter lookbooks by the user.
  • View the user’s profile picture on all lookbooks created by them.

  • View lookbooks.

User Account Features

  • Sign up and log in to create a user profile with a bio and profile picture.

  • View and manage all your lookbooks, including deleting those that are no longer relevant.

Noteworthy Features

This lookbook application integrates several advanced features that make it a robust platform for managing and displaying user-generated content. Below are some highlights that developers might find particularly interesting.

Integrating Cloudinary for Image Management

One of the standout features of this application is the integration of Cloudinary for managing images. Cloudinary's powerful image hosting and transformation capabilities make it an ideal choice for applications dealing with a lot of visual content.


Highlights:

  • CloudinaryField usage:

    Using CloudinaryField in Django models simplifies the process of storing image references and managing them. It integrates seamlessly with Django's ORM and forms, and automatically uploads images to Cloudinary when the form is saved. (See Dynamic Form Handling.)

from cloudinary.models import CloudinaryField

class Lookbook(models.Model):
    overlay_image = CloudinaryField('overlay_images')
  • Transformations and overlays:

    The application utilizes Cloudinary’s AI-powered capabilities to automatically enhance lookbook images. Users can customize their lookbooks by choosing image overlays, image orientation, border width, and border color. AI paints out the images to fit the selected orientation, ensuring that they are displayed correctly without manual adjustments. These features are supported by utility functions designed for efficient image processing.

    In addition, when handling user-generated profile images, AI-based transformations ensure that images are optimized and uniformly cropped and centered, regardless of the original image content.

    Here’s an example of an original and transformed profile picture, along with the code to generate it. Notice how the crop honors the face’s placement in the center.

    profile_url = CloudinaryImage(public_id).build_url(quality='auto', width=600, height=600, crop='auto', gravity='face')
    

  • Easy retrieval and display:
    Images stored in Cloudinary can be easily retrieved using their URLs, simplifying the process of displaying images in templates.

    For example, when a user selects a lookbook to view, its ID is passed to the display function in the views.py file.

    The display function then retrieves a list of all images linked to that lookbook. The transformed delivery URLs of these images stored in the Lookbook model are sent to the template for rendering.

def display(request, lookbook_id):
   lookbook = get_object_or_404(Lookbook, pk=lookbook_id)
   images = lookbook.images.all()
   return render(request, 'display.html', {'photos': images, 'lookbook': lookbook})
<!-- templates/display.html -->

 {% extends 'base.html' %}

 {% block content %}
<div class="container first-container">
   <!-- Display lookbook title -->
   <h2>{{ lookbook.title }}</h2>
   <!-- Display lookbook description -->
   <p>{{ lookbook.description }}</p>
</div>

<div class="container">
<div class="gallery-container" >
   <div class="gallery-row">
       {% for photo in photos %}
       <div class="gallery-col">
           <div class="gallery-image">
               <!-- Display transformed image -->
               <img src="{{ photo.transformed_image }}" alt="Image" class="img-fluid">
               <div class="gallery-overlay">
                   <!-- Link to view in a new browser tab -->
                 <a href="{{ photo.transformed_image }}" target="_blank"><h5>Click to View</h5></a>
               </div>
           </div>
       </div>
       {% endfor %}
   </div>
</div>

{% endblock %}

  • Scalability and reliability:
    Cloudinary handles large volumes of images and high traffic, providing a scalable solution that grows with your application. Its infrastructure ensures high availability and reliability.

User-Generated Content Management

Efficient and secure handling of user-generated content is crucial for any application. In this lookbook app, content management is personalized and user-specific.

Highlights:

  • User associations:
    Each lookbook is associated with a user through a ForeignKey relationship:
from django.contrib.auth.models import User

class Lookbook(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
  • Profile integration:
    User profiles are seamlessly integrated into the app. Every user has a corresponding Profile model, facilitating connections to multiple lookbooks. Here’s how the model is structured:
class Profile(models.Model):
   user = models.OneToOneField(User, on_delete=models.CASCADE)
   profile_picture = CloudinaryField('profile_pictures')
   profile_url = models.URLField(blank=True, null=True, default='https://res.cloudinary.com/yelenik/image/upload/avatar')
   bio = models.TextField(blank=True)
   my_lookbooks = models.ManyToManyField(Lookbook, blank=True)

In the HTML template that displays all lookbooks, each lookbook is associated with its creator's profile image, accessed via {{ lookbook.user.profile.profile_url }}.

def __str__(self):
       return self.user.username
div class="row">
   {% for lookbook in lookbooks %}
       <div class="col-md-4">
           <div class="card mb-4">
               <div class="card-body all">
                   <div class="d-flex align-items-center mb-3">
                       <img src="{{ lookbook.user.profile.profile_url }}" alt="{{ lookbook.user.username }}" class="rounded-circle mr-2" style="width: 40px; height: 40px;">
                       <h5 class="mb-0">{{ lookbook.user.username }}</h5>
                   </div>
                   <h5 class="card-title">{{ lookbook.title }}</h5>
                   <p class="card-text">{{ lookbook.description }}</p>
                   <a href="{% url 'display' lookbook.id %}" class="btn btn-primary card-btn">View</a>
               </div>
           </div>
       </div>
   {% empty %}
       <div class="col">
           <p>No lookbooks found.</p>
       </div>
   {% endfor %}
</div>

Dynamic Form Handling

Forms are essential for handling user inputs in any web application. This app uses LookbookForm and LookbookImageForm to manage lookbook creation and image uploads.

Highlights:

  • Custom forms: These forms include custom save methods to handle specific logic, such as ensuring that image uploads are handled automatically via the CloudinaryImage field type.
class LookbookForm(forms.ModelForm):
    class Meta:
        model = Lookbook
        fields = ['title', 'description', 'overlay_image']

    def save(self, commit=True):
        instance = super().save(commit=False)
        if commit:
            instance.save()
        return instance

Utility Function for Image Processing

To keep the codebase maintainable and consistent, utility functions handle the bulk of image-processing tasks. This ensures standardized image transformations and simplifies maintenance.

Highlights:

  • Centralized image processing:
    Functions likegenerate_transformation_options, get_public_id_from_url, and create_transformed_url are reused within the application to standardize image processing. These functions generate Cloudinary transformations based on user selections for the lookbook’s attributes, ensuring consistency during both creation and editing processes.

    This transformation, built using these functions, applies user-selected formatting to an original image, as shown below. If the chosen orientation requires reshaping, generative AI will outpaint the necessary sides. Additionally, a border with the selected width and color is added, and a chosen overlay image is applied with reduced opacity in the top right corner.

def generate_transformation_options(lookbook):

   # Determine width and height based on orientation
   if lookbook.orientation == 'portrait':
       width, height = 400, 500
   elif lookbook.orientation == 'landscape':
       width, height = 800, 600
   else:  # square
       width, height = 800, 800

   # Initialize transformation list if not already present
   transformation_options = {                    }

   if 'transformation' not in transformation_options:
       transformation_options['transformation'] = []

   # Apply border style if border width is not '0px'
   if lookbook.border_width != '0px':
       transformation_options['border'] = f'{lookbook.border_width}_solid_{lookbook.border_color}'
                  
   # Define base transformation options
   all_transformation = [
       {'quality': 'auto',
       'width': width,
       'height': height,
       'crop': 'pad',
       'background': 'gen_fill:ignore-foreground_true'}
   ]
   transformation_options['transformation'].insert(0, all_transformation)

   # Apply overlay image if provided
   if lookbook.overlay_image:
       overlay_transformation = [
           {'overlay': lookbook.overlay_image, 'gravity': 'north_east', 'width': 100, 'flags': 'layer_apply', 'x': 20, 'y': 20, 'opacity': 80}
       ]
       transformation_options['transformation'].insert(1, overlay_transformation)

   # Add scale transformation to make the width 800
   transformation_options['transformation'].insert(2, {'width': 800, 'crop': 'scale'})
   return transformation_options



def create_transformed_url(public_id, transformation_options):
   return CloudinaryImage(public_id).build_url(**transformation_options)

Efficient Data Querying and Filtering

Efficient data retrieval and filtering are vital for application performance. The lookbook app uses Django ORM to fetch and filter lookbooks based on user selections.

Highlights:

  • Optimized queries:
    Using Django ORM queries to fetch and filter lookbooks enhances performance.

  • User-specific data retrieval:

    Functions like all_lookbooks and my_lookbooks efficiently retrieve user-specific data.

def all_lookbooks(request):
    users = User.objects.all()
    user_ids = request.GET.getlist('user_ids')
    if not user_ids or 'all' in user_ids:
        user_ids = [str(user.id) for user in users]
    lookbooks = Lookbook.objects.filter(user__id__in=user_ids).order_by('title')
    return render(request, 'all_lookbooks.html', {'lookbooks': lookbooks, 'users': users, 'selected_user_ids': user_ids})

Robust URL Routing

A clear and intuitive URL routing scheme is essential for easy navigation within the application. The urls.py file defines clear URL patterns for accessing different views.

Highlights:

  • Clear URL patterns:
    Defined URL patterns make navigation intuitive.

  • Auth integration:

    Incorporating Django's built-in authentication views simplifies user authentication management.

from django.urls import path
from lookbook_app import views
from django.contrib.auth import views as auth_views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/login/', auth_views.LoginView.as_view(), name='login'),
    path('accounts/logout/', auth_views.LogoutView.as_view(), name='logout'),
    path('signup/', views.signup, name='signup'),
    path('create_lookbook/', views.create_lookbook, name='create_lookbook'),
    path('display/<int:lookbook_id>/', views.display, name='display'),
    path('profile/', views.profile, name='profile'),
    path('my_lookbooks/', views.my_lookbooks, name='my_lookbooks'),
    path('all_lookbooks/', views.all_lookbooks, name='all_lookbooks'),
    path('', views.all_lookbooks, name='all_lookbooks'),
    path('edit_lookbook/<int:lookbook_id>/', views.edit_lookbook, name='edit_lookbook'),
]

Conclusion

The lookbook application leverages Django to create a robust platform for managing and displaying user-generated content. Key features like Cloudinary integration for image management, dynamic form handling, centralized utility functions, and efficient data querying contribute to a well-structured and scalable application.

These aspects improve the user experience and streamline the development process, making this project a valuable example for Django/Python developers interested in advanced web application techniques.

Feel free to explore the project in GitHub and make enhancements that could benefit the overall functionality and performance.