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

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ pip install -r requirements.txt
### Env variables

* Then refer to `env.md` for environment variables and keep those in the `.env` file in the current folder as your project is in.

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

### Docker / docker-compose
in order to use docker, please run the next commands after cloning repo:
Expand All @@ -107,9 +110,10 @@ docker-compose -f docker/docker-compose.yml up

```
python manage.py migrate
uvicorn apiv2.main:app --reload
python manage.py runserver
```
- Then open http://localhost:8000/swagger/ in your browser to explore API.
- Then open http://localhost:8000/docs/ in your browser to explore API.

- After running API, Go to Frontend UI [React CRM](https://github.com/MicroPyramid/react-crm "React CRM") project to configure Fronted UI to interact with API.

Expand Down
18 changes: 1 addition & 17 deletions accounts/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -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

from django.db import migrations, models
import phonenumber_field.modelfields
Expand Down Expand Up @@ -78,20 +78,4 @@ class Migration(migrations.Migration):
'ordering': ('-created_at',),
},
),
migrations.CreateModel(
name='Tags',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('name', models.CharField(max_length=20)),
('slug', models.CharField(blank=True, max_length=20, unique=True)),
],
options={
'verbose_name': 'Tag',
'verbose_name_plural': 'Tags',
'db_table': 'tags',
'ordering': ('-created_at',),
},
),
]
22 changes: 6 additions & 16 deletions accounts/migrations/0002_initial.py
Original file line number Diff line number Diff line change
@@ -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

from django.conf import settings
from django.db import migrations, models
Expand All @@ -10,25 +10,15 @@ class Migration(migrations.Migration):
initial = True

dependencies = [
('common', '0001_initial'),
('contacts', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('leads', '0001_initial'),
('teams', '0001_initial'),
('accounts', '0001_initial'),
('contacts', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('common', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='tags',
name='created_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By'),
),
migrations.AddField(
model_name='tags',
name='updated_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By'),
),
migrations.AddField(
model_name='accountemaillog',
name='contact',
Expand Down Expand Up @@ -82,7 +72,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='account',
name='created_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='account_created_by', to='common.profile'),
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By'),
),
migrations.AddField(
model_name='account',
Expand All @@ -97,7 +87,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='account',
name='tags',
field=models.ManyToManyField(blank=True, to='accounts.tags'),
field=models.ManyToManyField(blank=True, to='common.tag'),
),
migrations.AddField(
model_name='account',
Expand Down
21 changes: 0 additions & 21 deletions accounts/migrations/0003_alter_account_created_by.py

This file was deleted.

30 changes: 15 additions & 15 deletions accounts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,31 @@
from phonenumber_field.modelfields import PhoneNumberField

from common import utils
from common.models import Org, Profile
from common.models import Org, Profile, Tag
from common.utils import COUNTRIES, INDCHOICES
from contacts.models import Contact
from teams.models import Teams
from common.base import BaseModel


class Tags(BaseModel):
name = models.CharField(max_length=20)
slug = models.CharField(max_length=20, unique=True, blank=True)
# class Tags(BaseModel):
# name = models.CharField(max_length=20)
# slug = models.CharField(max_length=20, unique=True, blank=True)


class Meta:
verbose_name = "Tag"
verbose_name_plural = "Tags"
db_table = "tags"
ordering = ("-created_at",)
# class Meta:
# verbose_name = "Tag"
# verbose_name_plural = "Tags"
# db_table = "tags"
# ordering = ("-created_at",)

def __str__(self):
return f"{self.name}"
# def __str__(self):
# return f"{self.name}"


def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super().save(*args, **kwargs)
# def save(self, *args, **kwargs):
# self.slug = slugify(self.name)
# super().save(*args, **kwargs)


class Account(BaseModel):
Expand Down Expand Up @@ -65,7 +65,7 @@ class Account(BaseModel):
# Profile, related_name="account_created_by", on_delete=models.SET_NULL, null=True
# )
is_active = models.BooleanField(default=False)
tags = models.ManyToManyField(Tags, blank=True)
tags = models.ManyToManyField(Tag, blank=True)
status = models.CharField(
choices=ACCOUNT_STATUS_CHOICE, max_length=64, default="open"
)
Expand Down
5 changes: 3 additions & 2 deletions accounts/serializer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from rest_framework import serializers

from accounts.models import Account, AccountEmail, Tags, AccountEmailLog
from accounts.models import Account, AccountEmail, AccountEmailLog
from common.models import Tag
from common.serializer import (
AttachmentsSerializer,
OrganizationSerializer,
Expand All @@ -14,7 +15,7 @@

class TagsSerailizer(serializers.ModelSerializer):
class Meta:
model = Tags
model = Tag
fields = ("id", "name", "slug")
Comment on lines 16 to 19
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.



Expand Down
6 changes: 3 additions & 3 deletions accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from rest_framework.generics import GenericAPIView

from accounts import swagger_params1
from accounts.models import Account, Tags
from accounts.models import Account
from accounts.serializer import (
AccountCreateSerializer,
AccountSerializer,
Expand All @@ -29,7 +29,7 @@
from teams.serializer import TeamsSerializer
from accounts.tasks import send_email, send_email_to_assigned_user
from cases.serializer import CaseSerializer
from common.models import Attachments, Comment, Profile
from common.models import Attachments, Comment, Profile, Tag
from leads.models import Lead
from leads.serializer import LeadSerializer

Expand Down Expand Up @@ -130,7 +130,7 @@ def get_context_data(self, **kwargs):
context["countries"] = COUNTRIES
context["industries"] = INDCHOICES

tags = Tags.objects.all()
tags = Tag.objects.all()
tags = TagsSerailizer(tags, many=True).data
Comment on lines +133 to 134
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


context["tags"] = tags
Expand Down
Empty file added apiv2/__init__.py
Empty file.
36 changes: 36 additions & 0 deletions apiv2/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# fastapi_app/main.py
import os
import django

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "crm.settings")
django.setup()

from fastapi import FastAPI, Response
from apiv2.routers import users, leads, auth, org
from fastapi import Request

app = FastAPI(title="CRM API", version="1.0.0")


@app.middleware("http")
async def log_request(request: Request, call_next):
body = await request.body()
print("Request body:", body) # or use proper logging
# response = await call_next(request)

org = request.headers.get("org")
if org is None:
# Optionally, handle the missing header (e.g., assign a default or raise an error)
org = "default_org_value"
# Store it in request.state for later access in endpoints
request.state.org = org
response: Response = await call_next(request)
return response

# Include routers
app.include_router(auth.router, prefix="/api/auth", tags=["Auth"])
app.include_router(org.router, prefix="/api/org", tags=["Org"])
app.include_router(users.router, prefix="/api/users", tags=["Users"])
app.include_router(leads.router, prefix="/api/leads", tags=["Leads"])
# app.include_router(leads.router, prefix="/api/leads", tags=["Leads"])
# app.include_router(tasks.router, prefix="/api/tasks", tags=["Tasks"])
52 changes: 52 additions & 0 deletions apiv2/routers/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# fastapi_app/routers/users.py
from common.models import User # Django model
import requests
from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel
from rest_framework_simplejwt.tokens import RefreshToken
from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import BaseUserManager
router = APIRouter()

class SocialLoginSerializer(BaseModel):
token: str

@router.post("/google", summary="Login through Google")
def google_login(payload: SocialLoginSerializer, request: Request):
access_token = payload.token


# 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

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")

# Get or create user using Django ORM
try:
user = User.objects.get(email=data['email'])
except User.DoesNotExist:
user = User(
email=data['email'],
profile_pic=data.get('picture', ''),
password=make_password(BaseUserManager().make_random_password())
)
user.save()

# Generate JWT tokens using Django's SimpleJWT
token = RefreshToken.for_user(user)
return {
"username": user.email,
"access_token": str(token.access_token),
"refresh_token": str(token),
"user_id": user.id
}



Loading