CS412
Spring 2026

Assignment 5: Web Application with Models, Forms, and Images; ORM Queries

due by 9:00 p.m. EDT on Friday 2/27/2026

Learning Objectives

After completing this assignment, students will be able to:


Pre-requisites: Assignments 4

Assignment 5 builds upon the case study you began in Assignment 3.

You must have completed Assignment 4 before you attempt Assignment 5.

This assignment requires the Python Imaging Library, Pillow to be installed. If you have not already installed it in your virtual environment do so now:

    # pipenv install Pillow

Case Study: Mini Insta

This assignment is part of an in-depth case study to develop a data-enabled web application. In the entire case-study, you will simulate the core features of the Instagram social networking application. Over the course of 5 parts, you will implement:

This assignment is the third and fourth parts of the case study.

Here is a sample implementation: MiniInsta part 3 (uploading images, update/delete profile and posts).

MiniInsta part 4 (followers, likes, comments, and news feed).

You do not need to match the data of this example, or even the way it is formatted. The look and feel of the application, as well as the data you dispaly, is entirely up to you.

Preliminaries

In your work on this assignment, make sure to abide by the collaboration policies of the course.

If you have questions while working on this assignment, please post them on Piazza! This is the best way to get a quick response from your classmates and the course staff.

For each problem in this problem set, we will be writing or evaluating some Python code. You are encouraged to use the VS Code IDE which will be discuss/presented in class, but you are welcome to use another IDE if you choose.

Important Guidelines: Comments and Docstrings

  • Refer to the class Coding Standards for important style guidelines. The grader will be awarding/deducting points for writing code that conforms to these standards.

  • Every program file must begin with a descriptive header comment that includes your name, username/BU email, and a brief description of the work contained in the file.

  • Every function/method must include a descriptive docstring that explains what the function does and identifies/defines each of the parameters to the function.


Continue working on the mini_insta Application

Continue to do this work within your existing Django project, within the mini_insta. application that you worked on in Assignment 4.

Task 1: Revising the Photo model; uploading Photos

In the previous iteration of this assignment (i.e., assignment 4), we created a model for a Photo and stored the image_url associated with an image stored elsewhere on the World-Wide-Web. In this iteration of the assignment, we will add storage for image objects within the Django media directory.

  1. Revise the Photo model, to add an additional attribute called image_file. This attribute will refer to an image file (not a URL) that is stored in the Django media directory.

    **Do not remove the image_url attribute, as we need this for backwards-compatibility with existing Photo.

    Add an accessor method called get_image_url, which will return the URL to the image. This URL will either be the URL stored in the image_url attribute (if it exists), or else the URL to the image_file attribute, i.e., image_file.url.

    Revise the __str__ method to be consistent with the way the actual image is stored, i.e., image_url vs. image_file.

    Recall that after you change models, you will need to run the makemigrations script and the migrate command to update your database.

    Important:

    • Before you can go on to step 2 (displaying a Post with Photos stored by image_file), you will need to add at least 1 Photo (and maybe even 2) to your database using the admin tool to upload the file.

    • When you add an Photo to the database, you will need to also associate it with a Post object. Take note of which Post (for which Profile) this is, so that you can test that the Photo(s) display properly (see below).

  2. Displaying a post. You might need some revisions to the code that displays Posts and Photos, e.g. the show_post.html page. Revise to ensure that you are using the get_image_url method to access the image’s URL.

    Verify that it works for both:

    • one the Posts for which you stored an image URL.

    • one the Posts for which you uploaded an actual image.

Revise the process for creating a Post. Specifically, you will need to revise the CreatePostForm, the create_post.html template, and the CreatePostView.

  1. In the CreatePostForm, revise to remove the image_url. While it is still part of the model for backwards-compatibility, it should no longer display for the user to enter a URL.

  2. In the create_post.html template, you will need to to create a form field to be able to select and upload one or more actual image files. For example:

    Use the form input type="file", as well as the attribute "multiple" to be able to select more than one. Be sure to give this field a name, so that you can look for it in the form processing code (below).

    Be sure to include the attribute enctype="multipart/form-data" in the <form> tag, so that the form will be able to send the image data over HTTP.

  3. In the CreatePostView class, you will need to modify the code in the form_valid method. Previously, you retrieved the image_url from the request.POST data and used that to create a Photo object. Comment this part out.

    In it’s place, you will need to read the data from self.request.FILES. Try reading files = self.request.FILES.getlist('files'), which will return a list of 0 to many files. You can process this with a loop to create and save Photo objects.

  4. Test everything! Start at this URL pattern: 'http://127.0.0.1:8000/mini_insta/. Go to any profile, and then use the create post form to upload an image. Test that the image gets stored in Django, and finally that it displays on the show_post page.

Important: Add files to git!

  • You’ve just reached a good stopping point.

  • This is an excellent time to add your files to git, commit your changes, and push your changes to GitHub before anything gets F@&#ed up.

Task 2: Updating an existing Profile

  1. Create a new form class UpdateProfileForm which inherits from forms.ModelForm.

    Be sure to specify the inner-class Meta, which relates this form to the Profile model. Specify the list of fields that this form should set (i.e., all of the data attributes of the Profile class, except NOT the user’s username and join_date, which should not be changeable).

  2. Create a class-based view called UpdateProfileView, which inherits from the generic UpdateView class. Be sure to specify the form this create view should use, i.e., the UpdateProfileForm. Also, specify the name of the template to use to render this form, which must be called mini_insta/update_profile_form.html.

  3. Create the template file mini_insta/update_profile_form.html, to render the HTML form.

    You may use your discretion/imagination about how this template should display the form fields, but you must include labels for each field to show what input is expected. Upon submission, your form will be handled by the generic UpdateView class, which will update the Profile record for this model and store the update in the database.

    On this page, include a ‘cancel’ button, which should stop the update operation and return to the profile page. For example:

  4. Edit the mini_insta project’s urls.py file. Create a URL mapping to route requests from the URL pattern 'profile/<int:pk>/update' to the UpdateProfileView view. Name this URL update_profile.

    Test your URL! Try this URL pattern: 'http://127.0.0.1:8000/mini_insta/profile/1/update (or a similar URL with a different primary key).

    You should see a form to update the profile, and it should be pre-filled with the existing data for this Profile record. Change something in the form, and use the submit button to submit it to the server.

    A complication: Implementing the get_absolute_url method

    Test out this URL again. Now when you submit the create_post_form.html template, you should see an error: No URL to redirect to. Either provide a url or define a get_absolute_url method on the Model.

    To solve this problem, you will need to implement the special method get_absolute_url on the Profile model class. Within that method, you will need to return the URL corresponding to the Profile that was updated. For example, if we just updated a Profile with primary key of 5, we should return the URL /mini_insta/profile/5.

    Upon submission you should be routed back to the profile page, on which you should see the update.

    If you have errors, resolve them now before continuing on to the next part.

    Finally, add an Update Profile link or button to the show_profile.html template, so that you can click that link and reach the update form. For example:

Important: Add files to git!

  • You’ve just reached a good stopping point.

  • This is an excellent time to add your files to git, commit your changes, and push your changes to GitHub before anything gets F@&#ed up.

Task 3: Updating and Deleting Posts

In this part, you will create a way for to the user to update or delete a Post. This will involve:

For example, notice the links for update and delete:

These buttons (actually links that look like buttons) link will connect to the URLs for the DeletePostView and UpdatePostView, respectively.

  1. Create a class DeletePostView, which inherits from the generic DeleteView class. This base class will do most of what we want, but we will need to override some methods below. Set the model and template_name attributes.

    Override the get_context_data method, which will provide the context data needed to support the delete_post_form.html. Specifically, this context must provide the variables:

    • post, which is a reference to the Post to be deleted.

    • profile, which is the Profile of the user who made the Post.

    Override the get_success_url(self) method, to return the URL to which the user should be redirected after a successful delete operation. Specifically, when a Post is deleted, the user should be redirected to the profile page of the user whose Post was deleted.

  2. Create the template file delete_post_form.html. This template will display the Post that will be deleted, and contain 2 small forms:

    • One to confirm the delete, which will send a POST back to the same URL at which you are trying to do the delete.

    • One to cancel the delete, which will send a GET to URL to redisplay the profile page.

      Hint: you will need to set the action attribute of the form to the URL to show the profile page. You will need to create a URL that returns to the profile page, but you have created this URL elsewhere in your code. Think about where you did this previously!

    For example:

  3. Create a new URL pattern to trigger the delete Post process. Use this URL pattern: post/<int:pk>/delete, and associate this URL with your DeletePostView class.

    A concrete example of such a URL would be:

    http://localhost:8000/mini_insta/post/11/delete

    which means to delete the Post with primary key of 11.

  4. Within your show_post.html template, you will need to add a link (titled delete). Within this link, you will need to create the correct URL pattern to trigger the URL defined in the previous step.

    {% url 'delete_post' post.pk %}
    

    where post is the name of Post object within the context of the template that displays the Post.

  5. Test that the new delete link works – it should show up in the show_post page. Now try to click the link. Check that it generates the correct URL (for example: http://localhost:8000/mini_insta/post/11/delete).

    If it does not generate a correct URL for the delete operation, go back and fix that now.

    After deleting a Post, the user should be redirected to the Profile page for the person whose Post was deleted.

  6. Repeat steps 1-5 for the update operation. Create a class UpdatePostView which inherits from the generic UpdateView class, create a template called update_post_form.html with confirmation/cancel options, and a URL pattern: post/<int:pk>/update, and associate this URL with your UpdatePostView class.

    The requirement for the update Post feature is to be able to update the caption text of the Post. After updating a Post, the user should be redirected to the show_post page for that Post.

  7. Test everything!!

Important: Add files to git!

  • You’ve just reached a good stopping point.

  • This is an excellent time to add your files to git, commit your changes, and push your changes to GitHub before anything gets F@&#ed up.

Task 4: Additional data models: Follow, Comment and Like

Read before Create

  • In this section, we will add 3 new data models for Follows, Comments, and Likes.

  • In this assignment, we will use the Django Admin tool to create data for these models, but we will not implement features to create these through our web application. We will return to creating these data objects in Assignment 7.

  1. Define a new data model called Follow, which encapsulates the idea of an edge connecting two nodes within the social network (e.g., when one Profile follows another Profile).

    A Follow relation will associate 2 Profiles. Use these data attribute names:

    • profile (a foreign key) indicating which profile is being followed (i.e., the “publisher”).

    • follower_profile (a foreign key) indicating which profile is doing the following (i.e., the “subscriber”)

    • timestamp (a date time field) indicating the time at which the follower began following the other profile.

    Note: Two ForeignKeys to the same model

    • Django will complain about a reverse_accessor, because you have 2 ForeignKeys with the same model.

      To fix this, add the parameters related_name="profile" and related_name="follower_profile" to each ForeignKey, respectively.

    Write a __str__ method so that you can view this Follow relationship as a string representation. It would be helpful to include the display names or usernames of both Profiles in this string representation. For example for a Follow relationship between Angela Merkel and Taylor Swift, we could create this string representation: Angela Merkel follows Taylor Swift.

    Use the admin tool to add several Follow relationships. You don’t need a lot, but perhaps N Follows for N Profiles to get started. Leave at least 2 Profiles without followers (for testing purposes), and to be able test adding followers later (in next week’s assignment).

  2. Write the following accessor methods on the Profile class:

    • A get_followers method, which will return a list of those Profiles who are followers this profile (i.e., subscribers who will see Posts from this profile). Providing this method will make it possible to call the method (among other places) in the template, i.e., to display a profile’s followers.

      This method will need to use the Django ORM (i.e., Follower.objects) and its methods to filter/retrieve matching Follower records.

      • This method must return a list of the followers’ Profiles (not a QuerySet or list of Follows). Pay attention to the data types.
    • A get_num_followers method, which will return the count of followers.

    • A get_following method on the Profile class, which will return a list of those Profiles followed by this profile (i.e., publishers to whose Posts this Profile is subscribed).

      Providing this method will make it possible to call the method (among other places) in the template, i.e., to display the profiles a user is following (subscribed to).

      This method will need to use the Django ORM (i.e., Follower.objects) and its methods to filter/retrieve matching Follower records.

      • This method must return a list of the Profiles being followed (not a QuerySet or list of Follows). Pay attention to the data types.
    • A get_num_following method, which will return the count of how many profiles are being followed.

  3. Write the following views, templates, and URLs:

    • Create the ShowFollowersDetailView and ShowFollowingDetailView view classes, each of which will inherit from the generic DetailView. These classes are DetailViews for a Profile, and will provide the context variable profile to their respective templates.

    • Create the templates show_followers.html and show_following.html, which will use the context object profile to call the accessor methods on the Profile model, to retrieve the required lists of Profiles to display.

    • Create the URL patterns 'profile/<int:pk>/followers' and 'profile/<int:pk>/following' to bring up the ShowFollowersDetailView and ShowFollowingDetailView, respectively.

    Modify the show_profile.html template to include displaying the count of Followers and Following, with links that bring up the views for each.

    For example:

  4. Define a new data model called Comment, which encapsulates the idea of one Profile providing a response or commentary on a Post.

    A Comment will associate the following data attributes:

    • post (a foreign key) indicating the Post to which this Comment is related.

    • profile (a foreign key) indicating which profile is doing the commenting.

    • timestamp (a date time field) indicating the time at which this Comment was created.

    • text (a text field) which is the substance of the Comment

    Write a __str__ method so that you can view this Comment as a string representation.

    Use the admin tool to add several Comments on at least 2 different Posts. You don’t need a lot, just enough for testing purposes.

    Create an accessor method on the Post class called get_all_comments to retrive all comments on a Post. This method will need to use the Django ORM (i.e., Comment.objects) and its methods to filter/retrieve matching Comments for a Post.

    Update the show_post.html page to add display of Comments below the Post caption.

  5. Define a new data model called Like, which encapsulates the idea of one Profile providing approval of a Post.

    A Like will associate the following data attributes:

    • post (a foreign key) indicating the Post to which this Like is related.

    • profile (a foreign key) indicating which profileis doing the liking.

    • timestamp (a date time field) indicating the time at which this Like was created.

    Write a __str__ method so that you can view this Like as a string representation.

    Use the admin tool to add several Likess on at least 2 different Posts. You don’t need a lot, just enough for testing purposes.

    Create an accessor method on the Post class called get_likes to retrive all likes on a Post. This method will need to use the Django ORM (i.e., Like.objects) and its methods to filter/retrieve matching Likes for a Post.

    Update the show_post.html page to add display of Like count above the Post caption. For example:

  6. Test everything! Start at this URL pattern: 'http://127.0.0.1:8000/mini_insta/.

Important: Add files to git!

  • You’ve just modified your database structure, and added some data records.

  • This is an excellent time to add your files to git, commit your changes, and push your changes to GitHub before anything gets F@&#ed up.

Task 5: Creating a Post Feed

  1. The Instagram “post feed” is a scrollable view that includes Posts, Photos, Comments, and Likes (and lots of ads!), customized to each user (Profile).

    Briefly, the post feed shows posts for each of the profiles being followed by a given user, with the most recent at the top.

    Write an accessor method get_post_feed on the Profile object, which will return a list (or QuerySet) of Posts, specifically for the profiles being followed by the profiles on which the method was called.

    Hint: it will be easiest to develop this by using the ORM to filter Posts.

    You might experiment with using the object managers (e.g., Follow.objects, Post.objects) at the Django python console to discover which methods to use to generate the list/QuerySet that you want.

    You may also use python tools (e.g., list comprehension, for loop, method calls) to build/refine results.

    Hint: begin with a simple if not perfect version, and then you can refine it later.

    Test this method at the console to prove that it returns some data, of the correct type, before you try to build the view/template for it.

  2. Create a new template called show_feed.html to display the post feed for a single Profile. The post feed should show each Post, along with the profile image and name of the person who wrote it.

    Create a new view class PostFeedListView which inherits from ListView, and associate it with the show_feed.html template. The show_feed.html template should display all of the Posts in the feed, and each Post should include the first Photo of the Post, the caption, Likes, and Comments.

    Create a new URL pattern: 'profile/<int:pk>/feed' and associate it with the PostFeedListView.

  3. Navigation

    • Change the “home” navigation link (e.g., the leftmost navigation icon in the example) from the bottom of the page so that it links to the post feed.

    • If you do not already have one, add a navigation link to the profile page (e.g., the rightmost navigation icon in the example).

    • Add a link from the post feed back to the profile page.

    For example:

  4. Test everything! Start at this URL pattern: 'http://127.0.0.1:8000/mini_insta/.

Important: Add files to git!

  • You’ve just reached a good stopping point, and added some data records.

  • This is an excellent time to add your files to git, commit your changes, and push your changes to GitHub before anything gets F@&#ed up.

Task 6: Search and Results

  1. Add a search feature to search user Profiles and Posts based on a text input. Implement a view class called SearchView which inherits from the generic ListView. Use 2 templates called search.html and search_results.html, and a URL pattern 'profile/<int:pk>/search' (i.e., the search is being done on behalf of the user with the profile specified by pk).

    In configuring the ListView, set the template_name to be 'search_results.html'. This will be the default way to display results of this search.

    Here are some implementation notes:

    • The search.html page should present an HTML form with a text area search box as well as a button to submit the search. Use an HTML form (not a model form, since this is not creating or updating model data). This form should collect a query to search against the data models.

      For example:

    • The search_results.html page should present two different listings of results: one for Profiles that match the query, and one for Posts that match the query. Consider how you would like this to appear, and use a separate loop for each.

      For example:

  2. In the SearchView class, you will need to implement (override the super-class version) several methods:

    • dispatch(self, request, *args, **kwargs): this method is called first to dispatch (handle) any request. Override this method to add an if statement, testing for the name query in self.request.GET.

      If the query is absent, return the search.html template, i.e., to provide the form to collect the query from the user. Be sure to include the profile context variable, since the template will need this variable to create the URLs.

      Otherwise, return the superclass version of dispatch, i.e., super().dispatch(request, *args, **kwargs), which will continue the work of the generic ListView.

    • get_queryset(self): the generic ListView uses this method to obtain the QuerySet of instance data (that matches the model attribute of a ListView. We did not set that attribute, because we do not want all objects to be returned, but rather we want to control which objects to return.

      In this method, write a query against the object manager (i.e., Post.objects) to obtain Posts that contain the query. Return the QuerySet of matching Posts.

    • get_context_data(self, **kwargs): this method returns the dictionary of context data that can be accessed from the template. Add the following to the context data:

      • the profile object (for whom we are doing this search)

      • the query (if present)

      • the posts that match the query, obtained from get_query_set. A post matches if the query is contained in its caption text.

      • the profiles that match the query, obtained from using the object manager (Profile.objects). A profile matches if they query text is found in its username, display name, or bio text.

  3. Edit the base.html template, and add a link to the bottom-of-screen navigation, to bring up the search page (in its own page view).

  4. Test everything! Start at this URL pattern: 'http://127.0.0.1:8000/mini_insta/.

Important: Add files to git!

  • You’ve just modified your database structure, and added some data records.

  • This is an excellent time to add your files to git, commit your changes, and push your changes to GitHub before anything gets F@&#ed up.

Deployment to cs-webapps

  1. Deploy your web application to the cs-webapps.bu.edu.

Follow the deployment instructions here.

  1. Test your web application on cs-webapps.bu.edu to ensure that everything works correctly.

  2. Resolve any deployment issues.


Submitting Your Work

10 points; will be assigned by the autograder, verifying that you have submitted the correct files/URL, and testing that you website exists at the specified URL. 90 points; will be testing your application and code review

Log in to GradeScope to submit your work.

Be sure to name your files correctly!

  1. Create a text file within your main django directory called mini_insta_url.txt, containing the URL to your web page, and nothing else.

    For example: https://cs-webapps.bu.edu/azs/cs412/mini_insta/.

    This file will be used by the autograder to locate your web page, so you must get the URL exactly correct, and you must not include any other text or code in the file.

  2. Add the teaching staff as collaborators to your GitHub repository with bu-cs412. Read-only access is fine.

    Create a text file called github_url.txt in the root of your project (e.g., django directory). Paste your GitHub URL in the file.

    Add these files to your git repository using

    `git add -A`.
    

    Commit this to your git repository, using

    `git commit -m "Added mini_insta_url.txt"`
    

    Push it to GitHub, using

    `git push origin main`
    
  3. Log in to GradeScope to submit your work.

    In the Gradescope upload page, upload these two files:

    • mini_insta_url.txt

    • github_url.txt

Notes: