Skip to content

Dev #557

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open

Dev #557

wants to merge 4 commits into from

Conversation

ashwin31
Copy link
Member

@ashwin31 ashwin31 commented Apr 8, 2025

Summary by CodeRabbit

  • New Features

    • Launched a FastAPI-based CRM API offering endpoints for Google authentication, leads management, organization creation, and user retrieval.
  • Documentation

    • Updated instructions for environment configuration and revised the API documentation URL.
  • Chores

    • Carried out extensive internal improvements including database migration refinements, dependency upgrades, and configuration enhancements for improved system stability and maintainability.

Copy link

coderabbitai bot commented Apr 8, 2025

Walkthrough

This pull request updates the project documentation and refines the database schema across multiple Django apps. It replaces the use of the deprecated “Tags” model with the new “Tag” model in models, serializers, and views. Several migrations have been updated, removed, or reordered to alter field references and dependencies, primarily shifting foreign key targets from hard-coded models to settings‑based user models. Additionally, a new FastAPI application with dedicated routers for authentication, leads, organizations, and users has been introduced along with middleware and improved environment variable management in settings and requirements.

Changes

Files Change Summary
README.md Added commands for copying ENV.md to .env, setting a SECRET_KEY, and instructions for running the API with uvicorn; updated API docs URL from /swagger/ to /docs/.
accounts/migrations/0001_initial.py, 0002_initial.py,
0003_alter_account_created_by.py
Removed the Tags model from initial migration, updated dependencies and field references (e.g. created_by and tags in account model), and deleted the migration altering created_by.
accounts/models.py,
accounts/serializer.py,
accounts/views.py
Removed the Tags class in favor of the new Tag model; updated import statements, ManyToManyField, and serializer references accordingly.
apiv2/main.py,
apiv2/routers/auth.py,
apiv2/routers/leads.py,
apiv2/routers/org.py,
apiv2/routers/users.py,
apiv2/schemas/user.py,
apiv2/utils.py
Introduced a new FastAPI app with middleware for logging and header handling; added routers for authentication (Google login), leads management (CRUD, bulk upload, comment/attachment handling), organization listing/creation, and user listing; added Pydantic models and token verification utility.
cases/migrations/* Updated migration timestamps, reordered dependencies, and removed the migration altering created_by in the Case model.
cms/migrations/0001_initial.py Updated migration timestamp only.
common/migrations/* Added new dependencies (e.g. on contenttypes), new models (AuditLog, CustomField, Tag, etc.), and updated field definitions for User, Org, and Profile; updated unique constraints; removed several migration files altering user profile pic, org API key, and user managers.
common/models.py Added new classes (UserPreference, AuditLog, Tag, TaggedItem, CustomField, CustomFieldValue, Notification) and updated the Org class for improved error messaging.
common/utils.py,
common/views.py
Modified several tuples (INDCHOICES, LEAD_STATUS, COUNTRIES, CURRENCY_CODES) by changing formatting and removing translation functions; removed import of deprecated Tags.
contacts/migrations/* Updated field definitions in the Contact model (user reference, optional fields) and reordered dependencies; removed multiple migration files for altering contact fields.
crm/settings.py Introduced the environ package for environment variable management and added variables for Google authentication.
emails/migrations/0001_initial.py Updated migration timestamp only.
events/migrations/0001_initial.py Updated migration timestamp and dependency order.
invoices/migrations/0001_initial.py Reordered dependencies and updated migration timestamp.
leads/migrations/*,
leads/models.py,
leads/serializer.py,
leads/views.py
Removed dependency on accounts’ Tags, updated created_by and tags fields in Lead model and migrations to use settings‑based user model and new Tag model; updated serializer and view import statements accordingly.
opportunity/migrations/*,
opportunity/models.py,
opportunity/serializer.py,
opportunity/views.py
Updated created_by and tags field definitions and dependencies in migrations and models to reference settings‑based user models and the new Tag model; removed an obsolete migration for created_by.
planner/migrations/0001_initial.py Updated migration timestamp and reordered dependency entries.
requirements.txt Upgraded versions of Django, drf‑spectacular, and wagtail; added new packages: uvicorn, fastapi, django‑environ, httpx, python‑multipart.
tasks/migrations/* Updated Task model migrations by removing blank=True and updating created_by and updated_by fields to reference settings‑based user model with dynamic related names; removed a migration altering task.created_by.
teams/migrations/* Updated Teams model migrations: modified user-related fields with dynamic related names and corrected db_table name; several migration files were removed.

Sequence Diagram(s)

sequenceDiagram
    participant C as Client
    participant M as log_request Middleware
    participant A as FastAPI Auth Router
    participant G as GoogleAPI
    participant D as Database
    C->>M: Send POST /api/auth/google with Google token
    M->>A: Forward request after header setup
    A->>G: Validate token against Google API
    G-->>A: Return user info JSON
    A->>D: Lookup/Create user by email
    D-->>A: Return user record
    A->>A: Generate JWT tokens using SimpleJWT
    A-->>C: Return username, access & refresh tokens, user ID
Loading
sequenceDiagram
    participant C as Client
    participant M as FastAPI Middleware
    participant L as Leads Router
    participant V as verify_token Utility
    participant DB as Database
    C->>M: Send request to create/retrieve/update lead
    M->>L: Pass request along with header info
    L->>V: Verify JWT token
    V-->>L: Return decoded token data
    L->>DB: Perform lead operation (e.g., create, update, fetch)
    DB-->>L: Return operation result
    L-->>C: Return response with lead data
Loading

Poem

Oh, nimble paws that type with glee,
A rabbit sings of change so free.
Migrations dance and APIs bloom,
Tags now turn to “Tag” to loom.
From settings to endpoints so bright,
CodeRabbit hops into the light.
Let’s celebrate this code delight!
🐰✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@code-review-doctor code-review-doctor bot left a comment

Choose a reason for hiding this comment

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

Some food for thought. View full project report here.

f.write(file_content)

# Create leads from file (process asynchronously)
create_lead_from_file.delay(file_path, user.id, profile.org.id)

Choose a reason for hiding this comment

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

Suggested change
create_lead_from_file.delay(file_path, user.id, profile.org.id)
create_lead_from_file.delay(file_path, user.id, profile.org_id)

profile.org.id performs a database read when id is evaluated. You could take advantage of Django's caching of related fields by using profile.org_id, which does not do a database read. More info.

Comment on lines +851 to +854
if profile.role == "ADMIN" or user.is_superuser or profile.id == comment_obj.commented_by.id:
comment_obj.comment = comment_data.comment
comment_obj.save()
return {"error": False, "message": "Comment updated successfully"}

Choose a reason for hiding this comment

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

Suggested change
if profile.role == "ADMIN" or user.is_superuser or profile.id == comment_obj.commented_by.id:
comment_obj.comment = comment_data.comment
comment_obj.save()
return {"error": False, "message": "Comment updated successfully"}
if (
profile.role == "ADMIN"
or user.is_superuser
or profile.id == comment_obj.commented_by_id
):
comment_obj.comment = comment_data.comment
comment_obj.save()
return {"error": False, "message": "Comment updated successfully"}

Same as above: consider comment_obj.commented_by_id

Comment on lines +885 to +887
if profile.role == "ADMIN" or user.is_superuser or profile.id == comment_obj.commented_by.id:
comment_obj.delete()
return {"error": False, "message": "Comment deleted successfully"}

Choose a reason for hiding this comment

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

Suggested change
if profile.role == "ADMIN" or user.is_superuser or profile.id == comment_obj.commented_by.id:
comment_obj.delete()
return {"error": False, "message": "Comment deleted successfully"}
if (
profile.role == "ADMIN"
or user.is_superuser
or profile.id == comment_obj.commented_by_id
):
comment_obj.delete()
return {"error": False, "message": "Comment deleted successfully"}

As above, consider comment_obj.commented_by_id

"""
Comprehensive audit trail system to track all user actions
"""
user = models.ForeignKey(User, related_name="audit_logs", on_delete=models.SET_NULL, null=True)

Choose a reason for hiding this comment

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

Suggested change
user = models.ForeignKey(User, related_name="audit_logs", on_delete=models.SET_NULL, null=True)
user = models.ForeignKey(
User, related_name="audit_logs", on_delete=models.SET_NULL, null=True, blank=True
)

Expect unwanted behavior if null and blank are different values: null controls if the database allows no value for user and blank controls if the application allows no value for user. Consider setting null and blank to the same value for user. Explained here.

Comprehensive audit trail system to track all user actions
"""
user = models.ForeignKey(User, related_name="audit_logs", on_delete=models.SET_NULL, null=True)
content_type = models.ForeignKey('contenttypes.ContentType', on_delete=models.CASCADE)

Choose a reason for hiding this comment

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

Django automatically creates a related_name if it's not set. If it were set then a more readable and explicit relationship is set up. More info.

Associates tags with any model using generic relations
"""
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, related_name="tagged_items")
content_type = models.ForeignKey('contenttypes.ContentType', on_delete=models.CASCADE)

Choose a reason for hiding this comment

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

As above, with an explicit related_name would be better.

"""
Stores the values for custom fields
"""
custom_field = models.ForeignKey(CustomField, on_delete=models.CASCADE)

Choose a reason for hiding this comment

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

Likewise, with an explicit related_name would be better.

Stores the values for custom fields
"""
custom_field = models.ForeignKey(CustomField, on_delete=models.CASCADE)
content_type = models.ForeignKey('contenttypes.ContentType', on_delete=models.CASCADE)

Choose a reason for hiding this comment

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

Again, with an explicit related_name would be better.

recipient = models.ForeignKey(User, related_name="notifications", on_delete=models.CASCADE)
title = models.CharField(max_length=200)
message = models.TextField()
link = models.CharField(max_length=255, null=True, blank=True)

Choose a reason for hiding this comment

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

Suggested change
link = models.CharField(max_length=255, null=True, blank=True)
link = models.CharField(max_length=255, default="", blank=True)

Again, consider replacing null=True with default="" (and blank=True to pass validation checks).

("follow_up", "Follow Up"),
("comment", "Comment"),
))
content_type = models.ForeignKey('contenttypes.ContentType', on_delete=models.CASCADE, null=True, blank=True)

Choose a reason for hiding this comment

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

As above, with an explicit related_name would be better.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

🔭 Outside diff range comments (7)
leads/views.py (3)

140-140: ⚠️ Potential issue

Undefined Tags model due to missing import

Tags is used here but not imported. This should be updated to use the Tag model instead.

-context["tags"] = TagsSerializer(Tags.objects.all(), many=True).data
+context["tags"] = TagsSerializer(Tag.objects.all(), many=True).data
🧰 Tools
🪛 Ruff (0.8.2)

140-140: Undefined name Tags

(F821)


169-174: ⚠️ Potential issue

Replace Tags with Tag model

This code block uses the Tags model which is no longer imported. Update to use the Tag model.

-tag = Tags.objects.filter(slug=t.lower())
+tag = Tag.objects.filter(slug=t.lower())
 if tag.exists():
     tag = tag[0]
 else:
-    tag = Tags.objects.create(name=t)
+    tag = Tag.objects.create(name=t)
🧰 Tools
🪛 Ruff (0.8.2)

169-169: Undefined name Tags

(F821)


170-173: Use ternary operator tag = tag[0] if tag.exists() else Tags.objects.create(name=t) instead of if-else-block

Replace if-else-block with tag = tag[0] if tag.exists() else Tags.objects.create(name=t)

(SIM108)


173-173: Undefined name Tags

(F821)


445-450: ⚠️ Potential issue

Replace Tags with Tag model

This code block also uses the Tags model which is no longer imported. Update to use the Tag model.

-tag = Tags.objects.filter(slug=t.lower())
+tag = Tag.objects.filter(slug=t.lower())
 if tag.exists():
     tag = tag[0]
 else:
-    tag = Tags.objects.create(name=t)
+    tag = Tag.objects.create(name=t)
🧰 Tools
🪛 Ruff (0.8.2)

445-445: Undefined name Tags

(F821)


446-449: Use ternary operator tag = tag[0] if tag.exists() else Tags.objects.create(name=t) instead of if-else-block

Replace if-else-block with tag = tag[0] if tag.exists() else Tags.objects.create(name=t)

(SIM108)


449-449: Undefined name Tags

(F821)

common/views.py (2)

799-803: ⚠️ Potential issue

Undefined Tags model due to missing import

Tags is used here but not imported. This should be updated to use the Tag model instead.

-tag_obj = Tags.objects.filter(name=tag).first()
+tag_obj = Tag.objects.filter(name=tag).first()
 if not tag_obj:
-    tag_obj = Tags.objects.create(name=tag)
+    tag_obj = Tag.objects.create(name=tag)
🧰 Tools
🪛 Ruff (0.8.2)

800-800: Tags may be undefined, or defined from star imports

(F405)


802-802: Tags may be undefined, or defined from star imports

(F405)


847-851: ⚠️ Potential issue

Replace Tags with Tag model

This code block also uses the Tags model which is no longer imported. Update to use the Tag model.

-tag_obj = Tags.objects.filter(name=tag).first()
+tag_obj = Tag.objects.filter(name=tag).first()
 if not tag_obj:
-    tag_obj = Tags.objects.create(name=tag)
+    tag_obj = Tag.objects.create(name=tag)
🧰 Tools
🪛 Ruff (0.8.2)

848-848: Tags may be undefined, or defined from star imports

(F405)


850-850: Tags may be undefined, or defined from star imports

(F405)

opportunity/views.py (2)

129-134: ⚠️ Potential issue

Inconsistent tag model usage.

While you've updated line 92 to use the new Tag model, this block still uses the old Tags model which is now deprecated. This will cause runtime errors since Tags is no longer available.

if params.get("tags"):
    tags = params.get("tags")
    for tag in tags:
-        obj_tag = Tags.objects.filter(slug=tag.lower())
+        obj_tag = Tag.objects.filter(name=tag.lower())
        if obj_tag.exists():
            obj_tag = obj_tag[0]
        else:
-            obj_tag = Tags.objects.create(name=tag)
+            obj_tag = Tag.objects.create(name=tag, org=request.profile.org)
        opportunity_obj.tags.add(obj_tag)
🧰 Tools
🪛 Ruff (0.8.2)

129-129: Tags may be undefined, or defined from star imports

(F405)


133-133: Tags may be undefined, or defined from star imports

(F405)


237-242: ⚠️ Potential issue

Another instance of inconsistent tag model usage.

This is another location where the old Tags model is still being used. This needs to be updated to use the new Tag model.

if params.get("tags"):
    tags = params.get("tags")
    for tag in tags:
-        obj_tag = Tags.objects.filter(slug=tag.lower())
+        obj_tag = Tag.objects.filter(name=tag.lower())
        if obj_tag.exists():
            obj_tag = obj_tag[0]
        else:
-            obj_tag = Tags.objects.create(name=tag)
+            obj_tag = Tag.objects.create(name=tag, org=request.profile.org)
        opportunity_object.tags.add(obj_tag)
🧰 Tools
🪛 Ruff (0.8.2)

237-237: Tags may be undefined, or defined from star imports

(F405)


241-241: Tags may be undefined, or defined from star imports

(F405)

♻️ Duplicate comments (1)
apiv2/routers/leads.py (1)

130-130: Use meaningful function names or unify logic.
Redefinition of get_leads from line 117 to 130 can be confusing. Merge them if they share the same purpose or rename them to reflect distinct endpoints, as recommended above.

🧰 Tools
🪛 Ruff (0.8.2)

130-130: Redefinition of unused get_leads from line 117

(F811)

🧹 Nitpick comments (31)
apiv2/schemas/user.py (1)

1-10: New Pydantic model looks good, but has a minor syntax issue.

The UserOut model for FastAPI is well structured with clear type annotations.

There's a missing space in line 9:

-    class Config:
-        from_attributes =True
+    class Config:
+        from_attributes = True
planner/migrations/0001_initial.py (1)

1-1: Reordered dependencies in migration

The timestamp in the comment has been updated and dependencies have been reordered. While this doesn't affect functionality directly, it's important to ensure that migrations run in the correct order.

Consider adding a comment explaining why the migration timestamp was updated and dependencies reordered to help other developers understand the reasoning behind these changes.

Also applies to: 16-17

opportunity/serializer.py (1)

11-14: Consider renaming TagsSerializer to match singular model name

While the model reference has been updated to the singular Tag, the serializer name remains plural as TagsSerializer. For consistency with the new model name, consider renaming this to TagSerializer.

-class TagsSerializer(serializers.ModelSerializer):
+class TagSerializer(serializers.ModelSerializer):
    class Meta:
        model = Tag
        fields = ("id", "name", "slug")
apiv2/utils.py (2)

2-5: Remove unused imports

There are several imports that aren't being used in this file:

  • os (line 2)
  • django (line 3)
  • APIRouter (line 5)
-# fastapi_app/main.py
-import os
-import django
-# fastapi_app/routers/products.py
-from fastapi import APIRouter, Depends, HTTPException, status
+# fastapi_app/utils.py
+from fastapi import Depends, HTTPException, status
🧰 Tools
🪛 Ruff (0.8.2)

2-2: os imported but unused

Remove unused import: os

(F401)


3-3: django imported but unused

Remove unused import: django

(F401)


5-5: fastapi.APIRouter imported but unused

Remove unused import: fastapi.APIRouter

(F401)


16-27: Improve exception handling in verify_token function

When catching exceptions and raising new ones, it's best practice to use raise ... from err syntax to preserve the exception chain for better debugging.

def verify_token(token: str = Depends(oauth2_scheme)):
    token_backend = TokenBackend(algorithm=ALGORITHM, signing_key=SECRET_KEY)
    try:
        # Decode the token and verify its signature and claims.
        validated_token = token_backend.decode(token, verify=True)
        return validated_token
-    except (TokenError, InvalidToken, TokenBackendError):
+    except (TokenError, InvalidToken, TokenBackendError) as err:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token",
            headers={"WWW-Authenticate": "Bearer"}
-        )
+        ) from err
🧰 Tools
🪛 Ruff (0.8.2)

23-27: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

crm/settings.py (1)

17-19: Add default values or error handling for environment variables

The current implementation will raise an exception if any of the Google environment variables are missing. Consider providing default values or more descriptive error messages.

-GOOGLE_CLIENT_ID = env("GOOGLE_CLIENT_ID")
-GOOGLE_CLIENT_SECRET = env("GOOGLE_CLIENT_SECRET")
-GOOGLE_LOGIN_DOMAIN = env("GOOGLE_LOGIN_DOMAIN")
+GOOGLE_CLIENT_ID = env("GOOGLE_CLIENT_ID", default="")
+GOOGLE_CLIENT_SECRET = env("GOOGLE_CLIENT_SECRET", default="")
+GOOGLE_LOGIN_DOMAIN = env("GOOGLE_LOGIN_DOMAIN", default="")

Or for required variables with better error messages:

-GOOGLE_CLIENT_ID = env("GOOGLE_CLIENT_ID")
+GOOGLE_CLIENT_ID = env("GOOGLE_CLIENT_ID", default=None)
+if GOOGLE_CLIENT_ID is None:
+    raise Exception("GOOGLE_CLIENT_ID environment variable is required for Google authentication")
accounts/models.py (1)

16-33: Proper removal of deprecated Tags model.

Commenting out the Tags class is a good approach if you're in a transitional phase. However, if the transition to the new Tag model is complete, consider removing this code entirely rather than leaving it commented out.

common/models.py (2)

14-15: Unused import for ContentType.
Since all references to ContentType are by string ("contenttypes.ContentType"), this import is not needed.

-from django.contrib.contenttypes.models import ContentType
🧰 Tools
🪛 Ruff (0.8.2)

14-14: django.contrib.contenttypes.models.ContentType imported but unused

Remove unused import: django.contrib.contenttypes.models.ContentType

(F401)


70-90: New UserPreference model.
The overall design looks good; however, be aware that the field timezone at line 82 overshadows the imported timezone from django.utils. Consider renaming the model field to avoid confusion:

- timezone = models.CharField(max_length=50, default="UTC")
+ time_zone = models.CharField(max_length=50, default="UTC")
🧰 Tools
🪛 Ruff (0.8.2)

82-82: Redefinition of unused timezone from line 11

(F811)

common/migrations/0001_initial.py (1)

189-191: Revisiting Org name and api_key
api_key is a TextField. If you know the max length is short, consider switching to CharField.

common/utils.py (2)

149-394: Loss of translation support for countries.

Removing the _() translation wrappers means internationalization is no longer supported for these country names. Confirm that this is intended and doesn't affect existing localization requirements.


398-555: Loss of translation support for currency codes.

Similarly, removing the _() translation wrappers from currency codes discontinues their localization. Ensure this aligns with your requirements, or consider retaining translation support if needed.

apiv2/routers/users.py (2)

1-10: Incorrect file path comment.

The comment on line 1 references "fastapi_app/routers/users.py" which appears to be an incorrect path. It should match your actual project structure.

-# fastapi_app/routers/users.py
+# apiv2/routers/users.py

7-9: Add error handling to the endpoint.

The current implementation lacks error handling. If the database query fails, the API will return a 500 error without a helpful message.

 @router.get("/")
 def list_users():
-    return [{"id": user.id, "email": user.email} for user in User.objects.all()]
+    try:
+        return [{"id": user.id, "email": user.email} for user in User.objects.all()]
+    except Exception as e:
+        raise HTTPException(status_code=500, detail=f"Failed to retrieve users: {str(e)}")
README.md (4)

95-98: Code block needs a language specifier.

The code block should have a language specifier for proper syntax highlighting.

-```
+```bash
 cp ENV.md .env
 sed -i "s|^SECRET_KEY=.*|SECRET_KEY=\"$(python3 -c 'import secrets; print(secrets.token_urlsafe(50))')\"|" .env

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

95-95: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

</details>

</details>

---

`112-116`: **Missing word in API documentation URL.**

There's a missing "the" before "API" in the documentation instructions.

```diff
 python manage.py migrate
 uvicorn apiv2.main:app --reload
 python manage.py runserver
-```
-- Then open http://localhost:8000/docs/ in your browser to explore API.
+```
+- Then open http://localhost:8000/docs/ in your browser to explore the API.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~116-~116: Possible missing article found.
Context: ...t:8000/docs/ in your browser to explore API. - After running API, Go to Frontend U...

(AI_HYDRA_LEO_MISSING_THE)

🪛 markdownlint-cli2 (0.17.2)

116-116: Bare URL used
null

(MD034, no-bare-urls)


113-113: Include port configuration in uvicorn command.

The uvicorn command doesn't specify a port, which might conflict with Django's runserver on port 8000.

-uvicorn apiv2.main:app --reload
+uvicorn apiv2.main:app --reload --port 8001

This ensures both the Django app and FastAPI app can run simultaneously on different ports.


116-116: Avoid using bare URLs in Markdown.

According to Markdown best practices, URLs should be properly formatted with link syntax.

-- Then open http://localhost:8000/docs/ in your browser to explore API.
+- Then open [http://localhost:8000/docs/](http://localhost:8000/docs/) in your browser to explore the API.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~116-~116: Possible missing article found.
Context: ...t:8000/docs/ in your browser to explore API. - After running API, Go to Frontend U...

(AI_HYDRA_LEO_MISSING_THE)

🪛 markdownlint-cli2 (0.17.2)

116-116: Bare URL used
null

(MD034, no-bare-urls)

apiv2/routers/auth.py (4)

1-1: Incorrect file path comment.

The comment at the top of the file incorrectly references "fastapi_app/routers/users.py" instead of auth.py.

-# fastapi_app/routers/users.py
+# apiv2/routers/auth.py

14-49: Add proper docstring and improve error handling.

The function lacks a docstring explaining its purpose and parameters. Additionally, the error handling could be improved to handle network issues when contacting Google's API.

 @router.post("/google", summary="Login through Google")
 def google_login(payload: SocialLoginSerializer, request: Request):
+    """
+    Authenticate a user using Google OAuth.
+    
+    Args:
+        payload: Contains the Google access token
+        request: FastAPI request object
+    
+    Returns:
+        Dict containing user info and JWT tokens
+        
+    Raises:
+        HTTPException: If the token is invalid or the Google API request fails
+    """
     access_token = payload.token
     
-
     # Validate token with Google
-    print("Google response:", access_token)
-    response = requests.get(
-        'https://www.googleapis.com/oauth2/v2/userinfo',
-        params={'access_token': access_token}
-    )
-    data = response.json()
-
-    print("Google response data:", data)
-    if 'error' in data:
-        raise HTTPException(status_code=400, detail="Invalid or expired Google token")
+    try:
+        response = requests.get(
+            'https://www.googleapis.com/oauth2/v2/userinfo',
+            params={'access_token': access_token},
+            timeout=10
+        )
+        response.raise_for_status()
+        data = response.json()
+        if 'error' in data:
+            raise HTTPException(status_code=400, detail="Invalid or expired Google token")
+        if 'email' not in data:
+            raise HTTPException(status_code=400, detail="Email not found in Google account data")
+    except requests.RequestException as e:
+        raise HTTPException(status_code=503, detail=f"Failed to communicate with Google API: {str(e)}")

34-41: Consider adding email verification check.

The current implementation creates a user account for any Google email without verifying if the email belongs to your organization or is verified by Google.

 try:
     user = User.objects.get(email=data['email'])
 except User.DoesNotExist:
+    # Check if email is verified by Google
+    if data.get('verified_email', False) is not True:
+        raise HTTPException(status_code=400, detail="Email not verified with Google")
+    
+    # Optional: Check if email belongs to your organization
+    # if not data['email'].endswith('@yourcompany.com'):
+    #    raise HTTPException(status_code=403, detail="Only company emails are allowed")
+    
     user = User(
         email=data['email'],
         profile_pic=data.get('picture', ''),
         password=make_password(BaseUserManager().make_random_password())
     )
     user.save()

50-52: Remove unnecessary empty lines.

There are several consecutive empty lines at the end of the file that should be removed.

     "refresh_token": str(token),
     "user_id": user.id
 }
-

-

-
requirements.txt (2)

19-20: Remove commented line.

There's a commented line "# remove it" which should be properly addressed. Either remove both the comment and the package, or keep the package and remove the comment.

 Redis==4.6.0
-# remove it
 django-phonenumber-field==7.1.0

Or if the package should be removed:

 Redis==4.6.0
-# remove it
-django-phonenumber-field==7.1.0

23-27: New dependencies for FastAPI integration.

The added packages support the new FastAPI application. These are appropriate for integrating FastAPI with Django.

Consider adding version pinning for sub-dependencies as well using a requirements.lock file to ensure consistent builds.

apiv2/main.py (2)

15-29: Use proper logging instead of print statements in production.
Line 18 uses a print("Request body:", body) statement. Printing to stdout in production may clutter logs and omit essential contextual data. Consider switching to Python’s built-in logging module or another robust logging solution.

-    print("Request body:", body)  # or use proper logging
+    import logging
+    logger = logging.getLogger("uvicorn")
+    logger.info("Request body: %s", body)

31-36: Remove or clarify commented-out router lines.
Commented lines 35–36 referencing leads.router and tasks.router might confuse future maintainers. If these routers are no longer needed, remove them. If they are planned for future use, add a brief comment explaining their purpose.

 # app.include_router(leads.router, prefix="/api/leads", tags=["Leads"])
-# app.include_router(tasks.router, prefix="/api/tasks", tags=["Tasks"])
+# app.include_router(tasks.router, prefix="/api/tasks", tags=["Tasks"])  # Uncomment once tasks router is ready
apiv2/routers/org.py (2)

16-32: Consider raising exceptions with raise ... from err and removing debug prints.

  1. Static analysis (B904) suggests using raise ... from ve in the except ValidationError as ve: block to better preserve tracebacks.
  2. The debug print call on line 28 (print(org_serialized)) could pollute logs in production. Consider removing or replacing with structured logging.
--- a/apiv2/routers/org.py
+++ b/apiv2/routers/org.py
@@ -28,7 +28,7 @@ def list_products(token_payload: dict = Depends(verify_token)):
     org_serialized = list(org.values('id', 'name'))
-    print(org_serialized)
+    # logger.debug("Serialized orgs: %s", org_serialized)
     return {
         "orgs": org_serialized,
         }
🧰 Tools
🪛 Ruff (0.8.2)

16-16: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


49-52: Raise from the caught exception for clarity.
Raising the original exception with context helps distinguish errors in exception handling from the actual error.

-        raise HTTPException(
+        raise HTTPException from ve(
             status_code=status.HTTP_400_BAD_REQUEST,
             detail=ve.message_dict
         )
🧰 Tools
🪛 Ruff (0.8.2)

49-52: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

apiv2/routers/leads.py (3)

1-3: Remove unused imports.
Form is imported but never used. Consider removing it to keep imports lean and the code clean.

-from typing import List, Optional, Any, Dict
-from fastapi import APIRouter, Depends, HTTPException, Query, File, UploadFile, Form, status, Path, Request
+from typing import List, Optional, Any, Dict
+from fastapi import APIRouter, Depends, HTTPException, Query, File, UploadFile, status, Path, Request
🧰 Tools
🪛 Ruff (0.8.2)

2-2: fastapi.Form imported but unused

Remove unused import: fastapi.Form

(F401)


185-190: Remove unused local variables (closed_leads_count, closed_leads, companies).
They’re not utilized later in the function, causing linter warnings (F841). Consider removing them to maintain a clean codebase.

     closed_leads_count = queryset_closed.count()
     closed_leads = list(queryset_closed[offset:offset+limit].values())
-    
     contacts = Contact.objects.filter(org=profile.org).values('id', 'first_name')
-    companies = Company.objects.filter(org=profile.org)
🧰 Tools
🪛 Ruff (0.8.2)

185-185: Local variable closed_leads_count is assigned to but never used

Remove assignment to unused variable closed_leads_count

(F841)


186-186: Local variable closed_leads is assigned to but never used

Remove assignment to unused variable closed_leads

(F841)


190-190: Local variable companies is assigned to but never used

Remove assignment to unused variable companies

(F841)


362-362: Raise exceptions with raise ... from err or raise ... from None.
Static analysis (B904) flags these except clauses. Use raise X from e to maintain proper traceback references.

Also applies to: 734-734, 848-848, 882-882, 915-915, 1058-1058, 1093-1093, 1125-1125

🧰 Tools
🪛 Ruff (0.8.2)

362-362: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27a0765 and b45d74c.

📒 Files selected for processing (56)
  • README.md (2 hunks)
  • accounts/migrations/0001_initial.py (1 hunks)
  • accounts/migrations/0002_initial.py (4 hunks)
  • accounts/migrations/0003_alter_account_created_by.py (0 hunks)
  • accounts/models.py (2 hunks)
  • accounts/serializer.py (2 hunks)
  • accounts/views.py (3 hunks)
  • apiv2/main.py (1 hunks)
  • apiv2/routers/auth.py (1 hunks)
  • apiv2/routers/leads.py (1 hunks)
  • apiv2/routers/org.py (1 hunks)
  • apiv2/routers/users.py (1 hunks)
  • apiv2/schemas/user.py (1 hunks)
  • apiv2/utils.py (1 hunks)
  • cases/migrations/0001_initial.py (1 hunks)
  • cases/migrations/0002_initial.py (3 hunks)
  • cases/migrations/0003_alter_case_created_by.py (0 hunks)
  • cms/migrations/0001_initial.py (1 hunks)
  • common/migrations/0001_initial.py (7 hunks)
  • common/migrations/0002_initial.py (6 hunks)
  • common/migrations/0003_alter_user_profile_pic.py (0 hunks)
  • common/migrations/0004_alter_profile_alternate_phone.py (0 hunks)
  • common/migrations/0005_org_api_key.py (0 hunks)
  • common/migrations/0006_alter_org_api_key.py (0 hunks)
  • common/migrations/0007_org_is_active.py (0 hunks)
  • common/migrations/0008_alter_user_managers.py (0 hunks)
  • common/migrations/0009_user_is_staff.py (0 hunks)
  • common/models.py (4 hunks)
  • common/utils.py (3 hunks)
  • common/views.py (1 hunks)
  • contacts/migrations/0001_initial.py (3 hunks)
  • contacts/migrations/0002_alter_contact_created_by.py (0 hunks)
  • contacts/migrations/0003_alter_contact_secondary_number_and_more.py (0 hunks)
  • contacts/migrations/0004_alter_contact_address.py (0 hunks)
  • contacts/migrations/0005_alter_contact_address.py (0 hunks)
  • crm/settings.py (1 hunks)
  • emails/migrations/0001_initial.py (1 hunks)
  • events/migrations/0001_initial.py (2 hunks)
  • invoices/migrations/0001_initial.py (2 hunks)
  • leads/migrations/0001_initial.py (3 hunks)
  • leads/migrations/0002_alter_lead_created_by.py (0 hunks)
  • leads/models.py (2 hunks)
  • leads/serializer.py (2 hunks)
  • leads/views.py (1 hunks)
  • opportunity/migrations/0001_initial.py (3 hunks)
  • opportunity/migrations/0002_alter_opportunity_created_by.py (0 hunks)
  • opportunity/models.py (2 hunks)
  • opportunity/serializer.py (2 hunks)
  • opportunity/views.py (2 hunks)
  • planner/migrations/0001_initial.py (2 hunks)
  • requirements.txt (2 hunks)
  • tasks/migrations/0001_initial.py (3 hunks)
  • tasks/migrations/0002_alter_task_created_by.py (0 hunks)
  • teams/migrations/0001_initial.py (2 hunks)
  • teams/migrations/0002_alter_teams_table.py (0 hunks)
  • teams/migrations/0003_alter_teams_created_by.py (0 hunks)
💤 Files with no reviewable changes (18)
  • common/migrations/0003_alter_user_profile_pic.py
  • common/migrations/0007_org_is_active.py
  • common/migrations/0009_user_is_staff.py
  • common/migrations/0005_org_api_key.py
  • common/migrations/0004_alter_profile_alternate_phone.py
  • common/migrations/0008_alter_user_managers.py
  • contacts/migrations/0003_alter_contact_secondary_number_and_more.py
  • tasks/migrations/0002_alter_task_created_by.py
  • cases/migrations/0003_alter_case_created_by.py
  • contacts/migrations/0002_alter_contact_created_by.py
  • leads/migrations/0002_alter_lead_created_by.py
  • contacts/migrations/0005_alter_contact_address.py
  • opportunity/migrations/0002_alter_opportunity_created_by.py
  • teams/migrations/0002_alter_teams_table.py
  • teams/migrations/0003_alter_teams_created_by.py
  • accounts/migrations/0003_alter_account_created_by.py
  • common/migrations/0006_alter_org_api_key.py
  • contacts/migrations/0004_alter_contact_address.py
🧰 Additional context used
🧬 Code Graph Analysis (14)
accounts/serializer.py (2)
accounts/models.py (3)
  • Account (36-140)
  • AccountEmail (143-163)
  • AccountEmailLog (165-183)
common/models.py (1)
  • Tag (592-609)
leads/views.py (1)
accounts/models.py (1)
  • Account (36-140)
opportunity/models.py (2)
accounts/models.py (1)
  • Account (36-140)
common/models.py (3)
  • Org (149-169)
  • Profile (172-214)
  • Tag (592-609)
leads/models.py (1)
common/models.py (2)
  • Org (149-169)
  • Tag (592-609)
leads/serializer.py (2)
accounts/models.py (1)
  • Account (36-140)
common/models.py (1)
  • Tag (592-609)
accounts/models.py (1)
common/models.py (2)
  • Org (149-169)
  • Tag (592-609)
apiv2/routers/auth.py (2)
common/models.py (2)
  • User (36-67)
  • save (557-560)
emails/views.py (1)
  • email (29-55)
apiv2/routers/org.py (2)
apiv2/utils.py (1)
  • verify_token (16-27)
common/models.py (4)
  • Profile (172-214)
  • User (36-67)
  • Org (149-169)
  • AuditLog (563-589)
common/models.py (3)
accounts/models.py (3)
  • Meta (91-95)
  • Meta (156-160)
  • Meta (176-180)
accounts/serializer.py (2)
  • Meta (17-19)
  • Meta (32-61)
emails/views.py (1)
  • email (29-55)
apiv2/main.py (4)
accounts/views.py (2)
  • get (150-152)
  • get (342-436)
common/views.py (10)
  • get (73-83)
  • get (144-202)
  • get (213-249)
  • get (335-368)
  • get (418-430)
  • get (437-441)
  • get (539-541)
  • get (588-625)
  • get (772-784)
  • get (824-829)
opportunity/views.py (2)
  • get (103-105)
  • get (318-389)
leads/views.py (2)
  • get (151-153)
  • get (358-361)
common/views.py (2)
accounts/models.py (1)
  • Account (36-140)
contacts/models.py (1)
  • Contact (12-83)
common/migrations/0001_initial.py (1)
common/models.py (1)
  • generate_unique_key (145-146)
opportunity/serializer.py (1)
common/models.py (1)
  • Tag (592-609)
apiv2/routers/leads.py (3)
apiv2/utils.py (1)
  • verify_token (16-27)
common/models.py (6)
  • Attachments (327-433)
  • Comment (217-301)
  • Org (149-169)
  • Profile (172-214)
  • Tag (592-609)
  • User (36-67)
leads/models.py (2)
  • Lead (33-134)
  • Company (20-31)
🪛 Ruff (0.8.2)
apiv2/utils.py

2-2: os imported but unused

Remove unused import: os

(F401)


3-3: django imported but unused

Remove unused import: django

(F401)


5-5: fastapi.APIRouter imported but unused

Remove unused import: fastapi.APIRouter

(F401)


23-27: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

opportunity/views.py

92-92: Tag may be undefined, or defined from star imports

(F405)

apiv2/routers/org.py

16-16: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


35-35: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


49-52: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

common/models.py

14-14: django.contrib.contenttypes.models.ContentType imported but unused

Remove unused import: django.contrib.contenttypes.models.ContentType

(F401)


82-82: Redefinition of unused timezone from line 11

(F811)

apiv2/routers/leads.py

2-2: fastapi.Form imported but unused

Remove unused import: fastapi.Form

(F401)


119-119: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


130-130: Redefinition of unused get_leads from line 117

(F811)


135-135: Do not perform function call Query in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


137-137: Do not perform function call Query in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


142-142: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


185-185: Local variable closed_leads_count is assigned to but never used

Remove assignment to unused variable closed_leads_count

(F841)


186-186: Local variable closed_leads is assigned to but never used

Remove assignment to unused variable closed_leads

(F841)


190-190: Local variable companies is assigned to but never used

Remove assignment to unused variable companies

(F841)


246-246: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


344-344: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


362-362: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


372-373: Use a single if statement instead of nested if statements

(SIM102)


465-465: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


466-466: Do not perform function call File in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


484-484: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


493-495: Use a single if statement instead of nested if statements

(SIM102)


563-563: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


564-564: Do not perform function call File in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


582-582: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


716-716: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


734-734: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


776-776: Do not perform function call File in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


777-777: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


830-830: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


848-848: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


864-864: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


882-882: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


897-897: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


915-915: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


945-948: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


978-978: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


999-999: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


1040-1040: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


1053-1053: Local variable profile is assigned to but never used

Remove assignment to unused variable profile

(F841)


1058-1058: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


1075-1075: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


1088-1088: Local variable profile is assigned to but never used

Remove assignment to unused variable profile

(F841)


1093-1093: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


1107-1107: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


1120-1120: Local variable profile is assigned to but never used

Remove assignment to unused variable profile

(F841)


1125-1125: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🪛 markdownlint-cli2 (0.17.2)
README.md

95-95: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


116-116: Bare URL used
null

(MD034, no-bare-urls)

🪛 LanguageTool
README.md

[uncategorized] ~116-~116: Possible missing article found.
Context: ...t:8000/docs/ in your browser to explore API. - After running API, Go to Frontend U...

(AI_HYDRA_LEO_MISSING_THE)

🔇 Additional comments (69)
cms/migrations/0001_initial.py (1)

1-1: The migration file timestamp has been updated.

This change updates the generation timestamp of the migration file, which is a metadata change and doesn't affect functionality.

emails/migrations/0001_initial.py (1)

1-1: The migration file timestamp has been updated.

This change updates the generation timestamp of the migration file, which is a metadata change and doesn't affect functionality.

accounts/migrations/0001_initial.py (1)

1-1:

❓ Verification inconclusive

The migration file timestamp has been updated and Tags model removed.

The timestamp has been updated, and according to the AI summary, the Tags model has been removed from this migration. The removal is part of the transition from the deprecated Tags model to the new Tag model across the codebase.


🏁 Script executed:

#!/bin/bash
# Check related models and references to Tags or Tag
rg -A 5 "Tag model|Tags model" --glob "*.py"

Length of output: 46


Action Required: Confirm Tags Model Removal and Migration Integrity

The migration file’s timestamp update appears correct. However, to be sure that the deprecated Tags model has been fully removed in favor of the new Tag model across the codebase, please run the verification script above. Once you confirm there are no lingering definitions or references to the "Tags" model, you can proceed with approving the changes.


🏁 Script executed:

#!/bin/bash
echo "Searching for definitions of the deprecated Tags model (class Tags(...)):"
rg "class Tags(" --glob "*.py"
echo ""
echo "Searching for definitions of the new Tag model (class Tag(...)):"
rg "class Tag(" --glob "*.py"

Length of output: 637


Action Required: Re-Verify Model Removal

The migration file timestamp update is correct and reflects the removal of the deprecated Tags model in favor of the new Tag model. However, the previous search commands produced regex errors. Please re-run the updated script above— which correctly escapes the parentheses—to confirm that there are no lingering references or definitions of the deprecated Tags model in the codebase.

Once you’ve verified that no unwanted references exist, the migration update can be finalized.


🏁 Script executed:

#!/bin/bash
echo "Searching for definitions of the deprecated Tags model (class Tags(...)):"
rg "class Tags\\(" --glob "*.py"
echo ""
echo "Searching for definitions of the new Tag model (class Tag\\(" --glob "*.py"

Length of output: 522


Migration Update Verification: Timestamp & Model Removal

  • The migration file’s timestamp is correctly updated.
  • The deprecated Tags model has been removed from active code. A commented-out reference (# class Tags(BaseModel):) remains in accounts/models.py; please verify if this artifact should be cleaned up.
  • No active references to the deprecated model were found, which aligns with the intended transition to the new Tag model.

Please review the commented legacy code and remove it if it is no longer needed.

accounts/serializer.py (1)

3-4: Properly replaced Tags model with Tag model

Good job migrating from the deprecated Tags model to the new Tag model in the serializer. This change aligns with the model updates in the rest of the codebase.

Make sure to update all references to Tags in other files as well to maintain consistency.

Also applies to: 18-18

opportunity/serializer.py (1)

4-4: Great model update from Tags to Tag

You've correctly updated the import and model reference in TagsSerializer to use the new Tag model from common.models instead of the deprecated Tags model.

Also applies to: 13-13

crm/settings.py (1)

8-19: Good enhancement to environment variable handling

The addition of environ.Env() provides a more robust way to manage environment variables compared to using os.environ directly. This will help with maintainability as the project grows.

leads/models.py (2)

7-7: Import update for Tag model is correct

The import has been updated to use Tag from common.models, which aligns with the project's move from the deprecated Tags model to the new Tag model.


64-64: Field updated to use the new Tag model

The tags field has been properly updated to reference the new Tag model from common.models instead of the deprecated Tags model. This change maintains the same functionality while using the improved tag implementation.

invoices/migrations/0001_initial.py (2)

1-1: Updated migration timestamp

The timestamp has been updated to reflect the regeneration of this migration file.


17-18: Reordered migration dependencies

The dependencies have been reordered to move the swappable AUTH_USER_MODEL dependency before the accounts app dependency. This reordering doesn't change the migration's functionality but might improve dependency resolution.

leads/serializer.py (2)

3-4: Updated imports for Tag model

Imports have been correctly updated to use the new Tag model from common.models instead of the deprecated Tags model from accounts.models.


19-19: Updated TagsSerializer to use the new Tag model

The serializer's Meta class has been updated to reference the new Tag model, maintaining compatibility while using the improved tag implementation.

opportunity/models.py (2)

6-7: Updated imports for Tag model

Imports have been properly updated to use the Tag model from common.models instead of the deprecated Tags model from accounts.models.


51-51: Field updated to use the new Tag model

The tags field has been correctly updated to reference the new Tag model, which provides enhanced functionality including organization scoping, color support, and descriptions.

teams/migrations/0001_initial.py (2)

28-28: Good improvement on user reference approach.

Updating the created_by field to use settings.AUTH_USER_MODEL instead of hardcoded 'common.profile' makes your code more flexible and maintainable. The dynamic related_name pattern '%(class)s_created_by' also helps prevent name collisions across models.


36-36: Fixed database table name.

Correcting the db_table value from 'eams' to 'teams' aligns the table name with the model name, which is good practice for clarity and maintainability.

accounts/migrations/0002_initial.py (3)

13-15: Proper dependency ordering.

Reordering the dependencies to include 'common', 'contacts', and the AUTH_USER_MODEL before they're referenced in the migration is a good practice that prevents migration errors.


75-75: Consistent user reference model.

Changing the created_by field to reference settings.AUTH_USER_MODEL with a dynamic related_name maintains consistency with changes in other models and allows for more flexibility in your user model implementation.


90-90: Improved tag model reference.

Updating the tags field to reference 'common.tag' instead of 'accounts.tags' correctly implements the transition to the centralized Tag model from common.

accounts/models.py (2)

9-9: Good addition of Tag import.

Correctly importing Tag from common.models is necessary for the updated field reference.


68-68: Updated tag model reference in Account model.

The change to reference the new Tag model in the ManyToManyField is consistent with the project-wide transition from model-specific Tags to a centralized Tag model.

opportunity/migrations/0001_initial.py (3)

16-18: Dependencies reordering looks correct.
No issues found. Pulling in settings.AUTH_USER_MODEL as a swappable dependency is a standard Django practice.


41-41: Foreign key refactor to settings.AUTH_USER_MODEL.
Switching from common.profile to settings.AUTH_USER_MODEL is a recommended approach that improves flexibility and compatibility with Django’s user model.


43-43: Updated tags field to point to common.tag.
This aligns with the new tagging system.

leads/migrations/0001_initial.py (3)

16-16: Dependency changes approved.
Removing the dependency on accounts while reordering the rest is consistent with the new relationships.


74-74: created_by now references settings.AUTH_USER_MODEL.
This harmonizes with the shift away from referencing common.profile directly.


76-76: tags field referencing common.tag.
No issues found; it complements the updated tagging system.

accounts/views.py (2)

17-17: Refined import statements.
Importing Account from accounts.models remains consistent with usage across this view file.


32-32: Added Tag to imports from common.models.
This ensures alignment with the unified tagging system.

common/models.py (7)

150-153: Customized error messages for Org.name.
Great addition to improve form validation user-friendliness.


562-563: No functional changes in these blank lines.


592-610: New Tag model.
Introducing color support and organization scoping aligns well with advanced tagging features.


612-629: New TaggedItem model.
Enabling generic relations for tags across different models is a solid approach to flexible tagging.


631-672: New CustomField model.
Allows administrators to define dynamic fields. Implementation looks strong.


674-692: New CustomFieldValue model.
Stores user-defined values, completing the custom fields feature. Nicely handled.


694-723: New Notification system.
Offers a flexible way to alert and inform users. Implementation details look good.

tasks/migrations/0001_initial.py (3)

1-1: Auto-generated migration timestamp
No issues with this line, as it is a standard Django comment.


16-18: Re-ordered migration dependencies
Be sure there's no circular dependency added by this reordering.

Would you like a script to check references in related apps for potential migration loops?


35-35: Confirm removal of blank=True
Removing blank=True means forms will now treat this field as required. Verify if that is desired behavior.

Please ensure no admin pages or forms break due to the stricter requirement.

contacts/migrations/0001_initial.py (5)

1-1: Auto-generated migration timestamp
No concerns found.


17-17: Swappable user model reference
Using settings.AUTH_USER_MODEL for user references aligns with best practices.


36-36: Optional secondary phone number
Making the secondary phone field optional is typical and appears correct.


43-43: Allow blank Twitter username
This is appropriate for optional social media fields.


48-48: Created_by references user model
Switching from a direct profile reference to settings.AUTH_USER_MODEL is consistent and flexible.

common/migrations/0001_initial.py (11)

1-1: Auto-generated migration timestamp
No issues with this line.


16-16: Added contenttypes dependency
This is standard when dealing with generic relationships or tagging.


28-28: Switched profile_pic to CharField
Double-check if storing file references in a CharField is sufficient, especially for long paths or actual file uploads.

Would you like me to search for any file-upload logic that still expects a FileField?


32-32: New is_staff field
Implementation looks correct and will integrate with Django’s built-in admin permissions.


94-112: Introduced AuditLog model
JSONField usage and optional IP/User-Agent fields are useful for auditing.


145-167: Added CustomField model
This provides flexible metadata. Implementation appears solid.


168-182: Added CustomFieldValue model
Stores the values for corresponding custom fields. Looks good.


202-221: New Tag model
Implementation and fields for color/description seem fine.


223-243: New UserPreference model
Allows per-user custom settings. No concerns.


245-261: New TaggedItem model
Associates tags using content types. Standard approach.


289-312: New Notification model
Captures notifications with type and optional content type reference.

common/utils.py (2)

30-30: Use consistent conjunction for business naming.

You've replaced "&" with "and" in multiple industry options. This minor textual update improves consistency, and it should not cause any functional issues.

Also applies to: 34-34, 40-40, 42-42


73-73: Check lead status naming consistency.

The newly added ("new", "New") entry is lowercase for the first value in the tuple. Verify that referencing "new" doesn't conflict with or cause confusion among other statuses that may use different naming conventions (e.g., ("in process", "In Process")).

common/migrations/0002_initial.py (8)

1-1: Migration timestamp updated.

This updated timestamp reflects the new schema state. No issues here.


15-16: Check the new migration dependencies order.

Multiple new dependencies have been introduced. Confirm that the order won't trigger circular imports or other dependency conflicts.

Also applies to: 19-24


38-72: Verify new fields for CustomField and CustomFieldValue.

These fields define important relationships (e.g., content_type, created_by, custom_field, updated_by). Ensure all on_delete behaviors align with your design to avoid unintended data loss.


148-167: Check new references in AuditLog.

The added fields (content_type, created_by, updated_by, user) may change how logs are stored. Confirm these references match your auditing strategy.


258-261: Unique together constraint for TaggedItem.

(tag, content_type, object_id) must now be unique. Confirm existing data is compatible, or handle duplicates before applying this migration.


262-265: Unique together constraint for Tag.

Uniqueness on (org, name) could cause collisions if tags share the same name in the same org. Validate data to avoid migration conflicts.


270-273: Unique together constraint for CustomFieldValue.

(custom_field, content_type, object_id) ensures a single value per field-object combination. This can break if multiple entries previously existed. Promptly verify existing data.


274-277: Unique together constraint for CustomField.

(org, name, model_name) is now unique. Ensure that older records using the same field name for different model contexts won't collide.

cases/migrations/0001_initial.py (1)

1-1: Migration timestamp updated.

This is a routine notation change to reflect the new generation time. No concerns.

requirements.txt (1)

1-1: Django version updated for security.

Upgrading Django from 4.2.1 to 4.2.20 is a good practice as it includes important security fixes.

cases/migrations/0002_initial.py (2)

15-17: Good practice to use settings.AUTH_USER_MODEL for references.
This ensures better compatibility with custom user models and future scalability. The dependency reordering here also follows recommended Django practices.


34-34: Enhanced flexibility with dynamic related names.
Using '%(class)s_created_by' and referencing settings.AUTH_USER_MODEL is a robust approach, aligning well with Django's best practices.

@@ -7,7 +7,7 @@
from rest_framework.response import Response
from rest_framework.views import APIView

from accounts.models import Account, Tags
from accounts.models import Account
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing import for Tags model

The Tags model has been removed from the import but is still used in multiple places throughout the file (lines 140, 169-173, 445-449). This will cause runtime errors when these lines are executed.

Fix the import by adding the Tag model from common.models:

-from accounts.models import Account
+from accounts.models import Account
+from common.models import Tag

Then replace all occurrences of Tags with Tag in the file.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from accounts.models import Account
from accounts.models import Account
from common.models import Tag

@@ -32,7 +32,7 @@
from rest_framework.response import Response
from rest_framework.views import APIView

from accounts.models import Account, Contact, Tags
from accounts.models import Account, Contact
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing import for Tags model

The Tags model has been removed from the import but is still used in multiple places in the file (lines 799-802, 847-851). This will cause runtime errors when these lines are executed.

Fix the import by adding the Tag model from common.models:

from accounts.models import Account, Contact
+from common.models import Tag

Then replace all occurrences of Tags with Tag in the file.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from accounts.models import Account, Contact
from accounts.models import Account, Contact
from common.models import Tag

Comment on lines 16 to 19
class TagsSerailizer(serializers.ModelSerializer):
class Meta:
model = Tags
model = Tag
fields = ("id", "name", "slug")
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Check for potential serializer field mapping issues

While the model has been updated, it's important to verify that the fields specified in the serializer (id, name, slug) are all available in the new Tag model. Based on the provided common/models.py snippet, the Tag model has additional fields like color and description that might be useful to include.

You may want to run the following command to verify field mapping:


🏁 Script executed:

#!/bin/bash
# Check the Tag model fields
echo "Checking Tag model fields..."
grep -A 15 "class Tag" common/models.py

Length of output: 1313


Action: Update the serializer field mapping for the Tag model.

It appears that the current serializer in accounts/serializer.py (lines 16–19) still references ("id", "name", "slug"). However, based on the updated Tag model in common/models.py, the model now includes the fields org, name, color, and description (along with the default id field), and it does not define a slug field. Please review whether:

  • The slug field is intended to be a computed property (in which case, add the necessary logic in the serializer) or it has been removed in favor of the new fields.
  • You need to include the new fields such as color and description (and possibly org if relevant) in your serializer.

Adjusting the serializer to correctly map to the current model is necessary to avoid runtime errors or unintended behavior.

@@ -1,4 +1,4 @@
# Generated by Django 4.2.1 on 2023-06-19 13:08
# Generated by Django 4.2.1 on 2025-04-06 07:18
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Warning: Modifying existing migration file could cause issues

Changing the timestamp and dependencies in an existing migration file (0001_initial.py) can cause problems if this migration has already been applied in any environment. Migration files should generally be treated as immutable once they've been committed, as changes can disrupt Django's migration system.

If you need to add a dependency on the common app, consider creating a new migration instead of modifying an existing one.

Also applies to: 15-15

Comment on lines +11 to +12
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove duplicate BASE_DIR definition

BASE_DIR is defined twice in the file - once at line 11 and again at line 39. This is redundant and could lead to confusion. Keep only one definition.

# Set the project base directory
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Take environment variables from .env file
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))


GOOGLE_CLIENT_ID = env("GOOGLE_CLIENT_ID")
GOOGLE_CLIENT_SECRET = env("GOOGLE_CLIENT_SECRET")
GOOGLE_LOGIN_DOMAIN = env("GOOGLE_LOGIN_DOMAIN")
# JWT_AUTH = {
...
}

-# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

Also applies to: 39-39

Comment on lines +133 to 134
tags = Tag.objects.all()
tags = TagsSerailizer(tags, many=True).data
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Incorrect serializer name for tags.
There seems to be a typo in the serializer class name (TagsSerailizer instead of TagsSerializer), which can break the code if the mismatch persists.

Below is a suggested fix:

- from accounts.serializer import (
-     AccountCreateSerializer,
-     AccountSerializer,
-     EmailSerializer,
-     TagsSerailizer,
-     ...
- )
+ from accounts.serializer import (
+     AccountCreateSerializer,
+     AccountSerializer,
+     EmailSerializer,
+     TagsSerializer,
+     ...
+ )

...

- tags = TagsSerailizer(tags, many=True).data
+ tags = TagsSerializer(tags, many=True).data
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tags = Tag.objects.all()
tags = TagsSerailizer(tags, many=True).data
- from accounts.serializer import (
- AccountCreateSerializer,
- AccountSerializer,
- EmailSerializer,
- TagsSerailizer,
- ...
- )
+ from accounts.serializer import (
+ AccountCreateSerializer,
+ AccountSerializer,
+ EmailSerializer,
+ TagsSerializer,
+ ...
+ )
...
tags = Tag.objects.all()
- tags = TagsSerailizer(tags, many=True).data
+ tags = TagsSerializer(tags, many=True).data

@@ -127,7 +183,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='attachments',
name='created_by',
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Validate updated attachments reference.

Switching created_by from common.profile to settings.AUTH_USER_MODEL can be breaking if existing references assume a Profile object. Make sure data migrations handle this transition smoothly.

Comment on lines +7 to +9
@router.get("/")
def list_users():
return [{"id": user.id, "email": user.email} for user in User.objects.all()]
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing authentication for user data endpoint.

This endpoint exposes user data without any authentication mechanism, which poses a security risk. User information should be protected.

Consider adding authentication middleware or a dependency to protect this route:

-@router.get("/")
+@router.get("/", dependencies=[Depends(verify_token)])
 def list_users():
     return [{"id": user.id, "email": user.email} for user in User.objects.all()]

You'll need to import the authentication dependency:

from apiv2.utils import verify_token
from fastapi import Depends

🛠️ Refactor suggestion

Implement pagination for scalability.

Fetching all users at once can lead to performance issues as your user base grows. Consider implementing pagination to limit the number of results returned.

-@router.get("/")
+@router.get("/")
 def list_users():
-    return [{"id": user.id, "email": user.email} for user in User.objects.all()]
+    return [{"id": user.id, "email": user.email} for user in User.objects.all()[:100]]  # Temporary limit

A better solution would be to implement proper pagination:

@router.get("/")
def list_users(skip: int = 0, limit: int = 100):
    users = User.objects.all()[skip:skip+limit]
    return [{"id": user.id, "email": user.email} for user in users]



# Validate token with Google
print("Google response:", access_token)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove debug print statements.

Debug print statements should not be included in production code as they could expose sensitive information and clutter logs.

-    print("Google response:", access_token)
     response = requests.get(
         'https://www.googleapis.com/oauth2/v2/userinfo',
         params={'access_token': access_token}
     )
     data = response.json()

-    print("Google response data:", data)
     if 'error' in data:
         raise HTTPException(status_code=400, detail="Invalid or expired Google token")

Also applies to: 27-27

Comment on lines +117 to +128
def get_leads(
request: Request,
token_payload: dict = Depends(verify_token)
):

return {
"status": force_strings(LEAD_STATUS),
"source": force_strings(LEAD_SOURCE),
"countries": force_strings(COUNTRIES),
"industries": force_strings(INDCHOICES),
}

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid overshadowing functions named get_leads.
There is a @router.get("/meta/") and another @router.get("/") both using get_leads, which triggers redefinition warnings (F811). Rename one of them to avoid confusion and maintain clarity.

 @router.get("/meta/", response_model=Dict[str, Any])
-def get_leads(
+def get_lead_metadata(
     request: Request,
     token_payload: dict = Depends(verify_token)
 ):
     ...
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_leads(
request: Request,
token_payload: dict = Depends(verify_token)
):
return {
"status": force_strings(LEAD_STATUS),
"source": force_strings(LEAD_SOURCE),
"countries": force_strings(COUNTRIES),
"industries": force_strings(INDCHOICES),
}
@router.get("/meta/", response_model=Dict[str, Any])
def get_lead_metadata(
request: Request,
token_payload: dict = Depends(verify_token)
):
return {
"status": force_strings(LEAD_STATUS),
"source": force_strings(LEAD_SOURCE),
"countries": force_strings(COUNTRIES),
"industries": force_strings(INDCHOICES),
}
🧰 Tools
🪛 Ruff (0.8.2)

119-119: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant