Skip to content

Conversation

@VedanshiAwasthi
Copy link

This PR introduces a set of critical backend improvements that enhance the database configuration, API response reliability, and testing infrastructure. These updates ensure a more maintainable, predictable, and production-ready system.

What’s Included

  1. Model Enhancements
    Added db_alias in meta for all MongoEngine models to support multiple database environments (prod, test).
    Ensures smoother integration and test isolation.

  2. Database Configuration
    Updated mongoengine.connect() to explicitly define uuidRepresentation and read_preference.
    Boosts compatibility with new MongoDB drivers and improves replica set behavior.

  3. View Refactor
    Improved response format consistency across all API endpoints.
    Unified error handling structure for better frontend-debugging and client parsing.

  4. URL Updates
    Cleaned and restructured urls.py for clarity and scalability.

  5. Testing Improvements
    ->Fixed test failures caused by outdated validation logic.
    ->Added a full testing environment setup:
    ->pytest.ini for centralized test config
    ->Seed scripts to initialize DB with sample products and categories.
    ->Developed integration + unit tests for:
    ->Category Creation / Update / Fetch
    ->Product CRUD workflows
    ->Asserted response structure, HTTP status codes, and DB mutations.

~~ Why It Matters~~
Production Resilience: Cleaner error handling prevents silent failures and speeds up debugging.
Test Reliability: Clear separation of test DB via db_alias and seeded data ensures consistent results.
Scalability: Refactored code and URLs prepare the app for future module expansion.
Developer Velocity: New tests catch regressions early and act as documentation for expected behavior.

VedanshiAwasthi and others added 27 commits March 1, 2025 09:59
…rror, adjusted Docker Compose setup, and verified API response in the browser.
… a product category and to fetch a list of products belonging to a particular category.
DEBUG = True

ALLOWED_HOSTS = []
ALLOWED_HOSTS = ["*"]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid '*'. This allows requests from any domain

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have removed '*' from ALLOWED_HOSTS and restricted it to localhost and 127.0.0.1 for development. Also ensured CORS_ALLOWED_ORIGINS only allow localhost addresses. Please let me know if any further changes are needed!

# ]


def home(request):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove these from urls.py.
https://nalexn.github.io/separation-of-concerns/

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion! I’ve removed the unnecessary logic from urls.py as it’s no longer needed.

environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: example
command: ["mongod", "--auth"]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Glad the setup is on point!

from django.contrib import admin
from .models import Product

# admin.site.register(Product) No newline at end of file

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comments which no longer is need. Applicable everywhere.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve removed the unnecessary comments to keep the code clean and more readable. Let me know if there's anything else I can improve!

@@ -0,0 +1,18 @@
from rest_framework.response import Response

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion! I’ll make sure to rename the file according to PEP 8 conventions for better readability and consistency.

import mongoengine
import datetime

class ProductCategory(mongoengine.Document):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can import all the necessary Classes from monogengine directly.

from mongoengine import Document, StringField, DateTimeField

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve updated the imports to directly include only the necessary classes from mongoengine in both CategoryModel and ProductModel

Comment on lines +7 to +8
created_at = mongoengine.DateTimeField(default=lambda: datetime.datetime.now(datetime.UTC))
updated_at = mongoengine.DateTimeField(default=lambda: datetime.datetime.now(datetime.UTC))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since all of the models would need created_at and updated_at. We can create a base class for setting them up and inherit them wherever you need.

class TimestampedDocument(Document):
    created_at = DateTimeField(default=datetime.datetime.utcnow)
    updated_at = DateTimeField(default=datetime.datetime.utcnow)

    meta = {'abstract': True}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve created a TimestampedDocument base class to handle the created_at and updated_at fields, and now Product and ProductCategory inherits from it. I’ve updated the save() method to use datetime.utcnow() for consistency with the TimestampedDocument base class.

Comment on lines +13 to +16
def save(self, *args, **kwargs):
self.updated_at = datetime.datetime.now(datetime.UTC)
return super(ProductCategory, self).save(*args, **kwargs)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great that you have overriden the method to update updated at field.
Explore signals to see how we can do this without override. https://www.tutorialspoint.com/mongoengine/mongoengine_signals.htm#:~:text=Signals%20are%20events%20dispatched%20by,receive%20signals%20from%20many%20senders.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion! I've explored MongoEngine signals and implemented the pre_save signal to automatically update the updated_at field.

Comment on lines +26 to +28



Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And linter to fix these issues

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. I will use linter (pylint) to identify and fix any issues, including removing unnecessary imports, formatting corrections, and adding docstrings where necessary.

from .CategoryModel import ProductCategory

class Product(mongoengine.Document):
name = mongoengine.StringField(max_length=255, required=True)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should title be unique?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the title field should be unique, as each product category needs to have a distinct identifier. Ensuring uniqueness will help prevent any potential issues with having multiple categories with the same name, which could lead to confusion or errors in the system.

class CategoryRepository:
@staticmethod
def create(title, description=None):
if ProductCategory.objects.filter(title=title).first():

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Get() instead of filter here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve updated both the create and update functions as per your suggestions. I replaced filter() with get() in the create function for better performance and applied proper exception handling in the update function.

def create(title, description=None):
if ProductCategory.objects.filter(title=title).first():
raise ValueError("Category with this title already exists.")
category = ProductCategory.objects.create(title=title, description=description)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check ProductCategory(title=title, description=description) creates document.
Also do check this out - https://www.dabapps.com/insights/django-models-and-encapsulation/

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have checked and confirmed that ProductCategory(title=title, description=description) is successfully creating the document as expected.


@staticmethod
def get_category_by_title(title):
return ProductCategory.objects.filter(title__iexact=title).first()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets store only upper/lowecase for category

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your suggestion. At present, the case-insensitive approach is being used and several existing functions depend on it, and they have been thoroughly tested. Transitioning to a case-sensitive implementation would require substantial changes and additional testing. For now, I would prefer to maintain the current approach, but I am open to revisiting this if necessary in the future.

class ProductRepository:

@staticmethod
def createProd(prod_details):
Copy link

@ashutoshkeshri ashutoshkeshri Apr 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add types in function definition.
This is for better readability.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the functions to include type hints as per your recommendation. This will improve the readability of the code and help with understanding the expected inputs and outputs for each function.

def seed_database():

# Clear existing data
ProductCategory.objects.delete()
Copy link

@ashutoshkeshri ashutoshkeshri Apr 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add error handling for db errors.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've implemented error handling for MongoDB connections and seeding. The db fixture handles connection issues and switches to mongomock if MongoDB is unavailable. The seed_database function also logs and handles exceptions for better debugging.

ProductCategory.objects.delete()
Product.objects.delete()

# Create categories

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets create multiple items for testing.
Also This function currently does many things. Can you create helper function to clear, generate data.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored seed_database function by creating helper functions for clearing data, generating categories, and creating products. Each function has been equipped with error handling and docstrings for better clarity and maintainability.

if mongodb_use_local:

mongoengine.connect(
db='test_products_db',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid using magic strings.
https://softwareengineering.stackexchange.com/questions/365339/what-is-wrong-with-magic-strings.

Let create constants and use it like below

MONGO_HOST = os.getenv('MONGO_HOST', 'localhost')
MONGO_PORT = int(os.getenv('MONGO_PORT', 27017))
MONGO_USER = os.getenv('MONGO_USER', '')
MONGO_PASS = os.getenv('MONGO_PASS', '')
MONGO_AUTH_DB = os.getenv('MONGO_AUTH_DB', 'admin')
TEST_DB_NAME = 'test_products_db'

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rrefactored the MongoDB connection to use constants for configuration values, retrieved from environment variables. This avoids magic strings, improving code maintainability and flexibility.

return is_mongodb_available()

@pytest.fixture(scope="function")
def db(mongodb_use_local):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add proper loggers and error handling

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added logging and improved error handling for MongoDB connections and database seeding. Replaced print statements with structured logs for better traceability and debugging.


categories = ProductCategory.objects(title__in=value)
if len(categories) != len(value):
raise serializers.ValidationError("Some categories are invalid.")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't gives us which item is invalid.
Can you also add what item is invalid. This will help in debugging data issues properly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants