A Customer Relationship Management for your clients and deals
Project features @ ver-1.9
- user login
- user registration
- user password reset by email
- leads & agents creation
- leads & agents management
- agents classification
- category creation & assignement
CRM-promo.mp4
Previous considerations:
- Python 3.9 installed
- Pip installed (comes with Python 3, no worries)
- Virtualenv installed (comes with Python 3, no worries)
- Python added to your local variables
If you have python 3.11 or other versions, it's necessary to configure a new environment because the default environment with which this project runs works under Python 3.9.
To configure a new environment with your Python version, do this:- Delete the
env
folder on the root of this project - Create a new env:
python -m venv env
Note: It's known that the package psycopg2-binary==2.9.5 is compatible with python 3.11 version and psycopg2-binary==2.9.3 (used in this project) is compatible with python 3.9 If you have python 3.11, be careful to make this change in the requirements.txt (meaning change to psycopg2-binary==2.9.5 as we use Python 3.9 that works with psycopg2 2.9.3)
- Delete the
This project uses PostgreSQL as database (like described in step 52), so you will need to follow that step to download and configure your database if you want to use PostgreSQL. But, if you just want to use the default sqlite3 database that comes with django and run the project as quick as possible. Follow these steps :)
- Clone the repository to your local folder
- Open the folder repo with VS code or git bash
- In VS terminal or git bash, input the following commands
env/Scripts/activate #for vscode # for git bash: source env/Scripts/activate pip install -r requirements.txt # don't forget to change here to psycopg2-binary==2.9.5 if you use python 3.11
- Comment out the postgresql database config under
crm/settings.py
, so that you can switch to sqlite3, and change it to the default sqlite3# DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.postgresql_psycopg2', # 'NAME': env("DB_NAME"), # 'USER': env("DB_USER"), # 'PASSWORD': env("DB_PASSWORD"), # 'HOST': env("DB_HOST"), # 'PORT': env("DB_PORT"), # } # } DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } }
- Continue the setup in the terminal, we are about to set this thing up :D
python manage.py collectstatic python manage.py makemigrations python manage.py migrate python manage.py createsuperuser #create a user and assign a password as you like python manage.py runserver #add a port if the django default port (8000) is in use by other apps on your machine
- Learn how to use below ...
- Navigate to the home page
http://192.168.0.127:8000/
and login with the superuser you created, add leads and agents as you want inhttp://192.168.0.127:8000/admin
or create your agent acc in the home page.
The pictures portray the results reached on every step and the versions keep track of the project's progress
- In MINGW
python -m venv enviroment source env/Scripts/activate
- In VSCODE
python -m venv enviroment env/Scripts/activate
pip freeze
pip install django==3.1.4
pip freeze > requirements.txt
django-admin startproject crm .
- Create the gitignire file and fill with:
https://github.com/github/gitignore/blob/main/Pythongitignore
python manage.py runserver # port if necessary
python manage.py migrate
python manage.py startapp leads
- In crm/setting.py, edit
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'leads' ]
-
In leads/models.py, create the model Lead as a class
class Lead(models.Model): first_name = models.CharField(max_length=20) last_name = models.CharField(max_length=20) age = models.IntegerField(default=0)
-
In the terminal
python manage.py makemigrations # create the 001_initial.py, auth_user is created, db.sqlite3 needs to be deleted in custom user python manage.py migrate # create the database/applying leads.0001_initial
-
In VS CODE install SQLite to see the database
- In leads/models.py
class Agent(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE)
-
In leads/models.py
from django.contrib.auth.models import AbstractUser class User(AbstractUser)L pass
-
In crm/settings.py
AUTH_USER_MODEL = "leads.User"
-
Delete 001_initial.py and db.sqlite3 files
python manage.py makemigrations python manage.py migrate
- Check the leads in the python shell
python manage.py shell
Output:from leads.models import Lead Lead.objects.all()
<QuerySet []>
- Creating a superuser
python manage.py createsuperuser
- Check the user, in the terminal
python manage.py shell
Output:from django.contrib.auth import get_user_model User = get_user_model() User.objects.all()
<QuerySet [<User: jose>]>
Output:from leads.models import Agent admin_user = User.objects.get(username="jose") admin_user
<User: jose>
agent = Agent.objects.create(user=admin_user) exit()
-
Edit leads/models.py
class Agent(models.Model): user = models.OneToOnefield(User, on_delete=models.CASCADE) def __str__(self): return self.user.email
-
In ther terminal
python manage.py shell
from leads.models import Agent Agent.objects.all()
Output:
<Queryset [<Agent: jose@mail.com>]>
from leads.models import Lead jose_agent = Agent.objects.get(user__email="jose@mail.com") jose_agent
Output:
<Agent: jose@mail.com>
Lead.objects.create(first_name="Joe", last_name="Soap", age=35, agent=jose_agent)
Output:
<Lead: LEAD OBJECT (1)>
-
Edit leads/models/py
class Lead(models.Model): first_name = models.CharField(max_length=20) last_name = models.CharField(max_length=20) age = models.IntegerField(default=0) def __str__(self): return f"{self.first_name} {self.last_name}" class Agent(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) def __str__(self): return self.user.email
-
In the terminal
python manage.py shell from lead.models import Lead Lead.objects.all()
Output:
<QuerySet [<Lead: Joe Soap>]>
- Run the server and go to
http://127.0.0.1/admin
- Login with the superuser: jose
- Add the user to show up in the admin site, go to leads/admin.py
from .models import User, Lead, Agent admin.site.register(User) admin.site.register(Lead) admin.site.register(Agent)
- Check
http://127.0.0.1/admin
output:Leads Agents +Add Change Leads +Add Change Users +Add Change
- Configure the output of Agent in leads/models.py
def __str__(self): return self.user.username
- The User/Agents/Leads can be created/modified/deleted in the
http://127.0.0.1/admin
- In leads/views.py
from django.http import HttpResponse def home_page(request): return HttpResponse("Hello World")
- In crm/urls.py
from leads.views import home_page urlpatterns = [ path('admin/', admin.site.urls), path('', home_page) ]
-
Inside the app leads create the forlders templates/leads
-
Inside leads/templates/leads create & edit the file home_page.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset = "UTF-8"> <meta name = "viewport" content="width=device-width", initial-scale=1.0> <title>Document</title> </head> <body> <h1>Hello world</h1> <p>Here is our HTML template</p> </body> </html>
-
Go to leads/views.py
def home_page(request): return render(request,"leads/home_page.html" )
-
Create a general 'templates' folder in crm (crm/templates)
-
Make the folder searcheable
-
Edit crm/settings.py
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.Djangotemplates', 'DIRS': [BASE_DIR / "templates"], } ]
-
Create the html in crm/templates/second_page.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset = "UTF-8"> <meta name = "viewport" content="width=device-width, initial-scale=1.0> <title>Document</title> </head> <body> <h1>Hello world</h1> <p>This is the second page</p> </body> </html>
-
Go to leads/views.py
def home_page(request): return render(request, "second_page.html" )
- Create the context variable in leads/views.py
def home_page(request): context = { "name" : Joe", "age" : 35 } return render(request,"second_page.html",context)
- In templates/second_page.html
<body> <h1>Hello world</h1> <p>This is the second page</p> {{ name }} {{ age }} </body>
- Using the database, looping the data
- In leads/views.py
from .models import Lead def home_page(request): leads = Lead.objects.all() context = { "leads": leads } return render(request,"secon_page.html",context)
- In templates/second_page.html
<body> <ul> {% for lead in leads %} <li>{{ lead }} </li> {% endfor %} </ul> </body>
-
Create & edit leads/urls.py
from django.urls import path from .views import home_page app_name = "leads" urlpatterns = [ path('all/', home_page) OR path('', home_page) ]
-
Edit crm/urls.py delete -> from leads.views import home_page delete -> path('', home_page)
from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('leads/', include('leads.urls', namespace="leads")) ]
Test: navigate to http://127.0.0.1:8000/leads/all
OR http://127.0.0.1:8000/leads/
Compiled in the branch of ver-1.0
- Change home_page.html name to lead_list.html (leads/templates/leads/lead_list.html) and edit leads/views.py
def home_page(request)
->def lead_list
def lead_list(request): leads = Lead.objects.all() context = { "leads": leads } return render(request, "leads/lead_list.html", context)
- Edit leads/urls.py
from django.urls import path from .views import lead_list app_name = "leads" urlpatterns = [ path('', lead_list) ]
- Add style to lead_list.html
<title>Document</title> <style> .lead { padding-top: 10px; padding-bottom: 10px; padding-left: 6px; padding-right: 6px; margin-top: 10px; background-color: #f6f6f6; width: 100%; } </style> <body> <h1> This is all of our leads</h1> {% for lead in leads %} <div class="lead"> {{ lead.first_name }} {{ lead.last_name }}. Age: {{ lead.age }} </div> {% endfor %} </body>
Test in: http://127.0.0.1:8000/leads/
- Create another view for the details of the leads in leads/views.py
def lead_detail(request, pk): print(pk) lead = Lead.objects.get(id=pk) return HttpResponse("here is the detail view")
- Import lead_detail in lead/urls.py
from .views import lead_list, lead_detail app_name = "leads" urlspatterns = [ path('', lead_list), path('<int:pk>', lead_detail) #this is going to add a path according to the ID (primary key) of the user (pk)
- Add links to the lead in leads/templates/leads/lead_list.html
Test:
<body> <a href="/leads/create">Create a new lead</a> <h1> This is all of our lead lead</h1> {% for lead in leads %} <div class="lead"> <a href="/leads/{{ lead.pk }}/"> {{ lead.first_name }} {{ lead.last_name }}</a>. Age: {{ lead.age }} </div> {% endfor %} </body>
http://127.0.0.1:8000/leads/1/
- Modify the lead_detail, in leads/views.py
def lead_detail(request, pk): lead = Lead.objects.get(id=pk) context = { "lead": lead } return render(request, "leads/lead_detail.html", context)
- Create & edit the html file templates/leads/lead_detail.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .lead { padding-top: 10px; padding-bottom: 10px; padding-left: 6px; padding-right: 6px; margin-top: 10px; background-color: #f6f6f6; width: 100%; } </style> </head> <body> <a href="/leads">Go back to leads</a> <hr /> <h1> This is the details of {{ lead.first_name }}</h1> <p> This persons age: {{ lead.age }} </p> </body> </html>
- Edit the PATH leads/urls.py
Test 20-3:
from .views import lead_list, lead_detail app_name = "leads" urlpatterns = [ path('', lead_list), path('<int:pk>/', lead_detail) ]
http://127.0.0.1:8000/leads/1/
-
Create the lead_create in leads/views.py
def lead_create(request): return render(request, "leads/lead_create.html")
-
Create the html file: templates/leads/lead_create.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <a href="/leads">Go back to leads</a> <hr /> <h1> Create a new lead </h1> </body> </html>
-
Edit the PATH leads/urls.py
from .views import lead_list, lead_detail, lead_create app_name = "leads" urlpatterns = [ path('', lead_list), path('<int:pk>/', lead_detail), path('create', lead_create), ]
Test 21-1:
http://127.0.0.1/leads/create
-
Create & edit the file forms in crm/leads/forms.py
from django import forms class LeadForm(forms.Form): first_name = forms.CharField() last_name = forms.CharField() age=forms.IntegerField(min_value=0)
-
Import Leadform to leads/views.py
from .forms import LeadForm def lead_create(request): context = { "form": LeadForm() } return render(request, "leads/lead_create.html", context)
-
Configure the form html and add security to the form: leads/lead_create.html
<body> <a href="/leads"> Go back to leads</a> <hr /> <h1> Create a new lead</h1> <form method="post"> <!-- form method="post" action="/leads/another-url/"> --> {% csrf_token %} {{ form.as_p }} <button type="submit" >Submit</button> </form> </body>
-
Grab the data from the POST form: leads/views.py
from .models import Lead, Agent def lead_create(request): form = LeadForm() if request.method == "POST": print('Receiving a post request') form = LeadForm(request.POST) if form.is_valid(): print("the form in valid") print(form.cleaned_data) first_name = form.cleaned_data['first_name'] last_name = form.cleaned_data['last_name'] age = form.cleaned_data['age'] agent = Agent.objects.first() Lead.objects.create( first_name=first_name, last_name=last_name, age=age, agent=agent ) print("The lead has been created") context = { "form": LeadForm() } return render(request, "leads/lead_create.html", context)
Test 21-2: Go to
Compiled in the branch ofhttp://127.0.0.1:8000/leads/create/
and create a lead, then SUBMIT
Verify inhttp://127.0.0.1:8000/leads/
Verify the prompt in the VS code or check inhttp://127.0.0.1:8000/admin/leads/lead/
ver-1.1
-
Redirect to
http://127.0.0.1:8000/leads/
after creating a user In leads/view.pyfrom django.shortcuts import render, redirect : : agent = Agent.objects.first() Lead.objects.create( first_name=first_name, last_name=last_name, age=age, agent=agent ) return redirect("/leads") :
-
Edit leads/forms.py
from django import forms from .models import Lead class LeadModelForm(forms.ModelForm): class Meta: model = Lead fields = ( 'first_name', 'last_name', 'age', 'agent', )
-
Simplify using LeadModelForm: edit leads/views.py, change LeadForm -> LeadModelForm
from .forms import LeadModelForm def lead_create(request): form = LeadModelForm() if request.method == "POST": form = LeadModelForm(request.POST) if form.is_valid(): form.save() return redirect("/leads") context = { "form": form } return render(request, "leads/lead_create.html", context)
-
Edit leads/lead_detail.html
<body> <a href="/leads"> Go back to leads</a> <hr /> <h1>This is the details of {{ lead.first_name }}</h1> <p>This persons age: {{ lead.age }} </p> <p>The agent responsible for this lead is : {{ lead.agent }}</p> </body>
Test 21-3: Go to
http://127.0.0.1:8000/leads/
- In lead/views.py
def lead_update(request, pk): lead = Lead.objects.get(id=pk) form = LeadForm() if reques.method == "POST": form = LeadForm(request.POST) if form.is_valid(): first_name = form.cleaned_data['first_name'] last_name = form.cleaned_data['last_name'] age = form.cleaned_data['age'] lead.first_name = first_name lead.last_name = last_name lead.age = age lead.save() return redirect("/leads") context = { "form": form, "lead": lead } return render(request, "leads/lead_update.html", context) ```
- Edit leads/urls.py
from .views import lead_list, lead_detail, lead_create, lead_update app_name = "leads" urlpatterns = [ path('', lead_list), path('<int:pk>/', lead_detail), path('<int:pk>/update/', lead_update), path('create'/, lead_create) ] ```
- Create the templates/leads/lead_update.html file
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <a href="/lead">Go back to leads</a> <hr /> <h1>Update lead: {{ lead.first_name }} {{ lead.last_name }}</h1> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Submit</button> </form> </body> </html>
- Simplify the lead_update model with LeadModelform in templates/leads/views.py
Test 23.1 Go to
def lead_update(request, pk): lead = Lead.objects.get(id=pk) form = LeadModelForm(instance=lead) if request.method == "POST": form = LeadModelForm(request.POST, instance=lead) if form.is_valid(): form.save() return redirect("/leads") context = { "form": form, "lead": lead } return render(request, "leads/lead_update.html", context)
http://127.0.0.1:8000/leads/1/update/
and update/edit
- In lead/views.py, create the lead_delete model
def lead_delete(request, pk): lead = Lead.objects.get(id=pk) lead.delete() return redirect("/leads")
- Edit the leads/urls.py
from .views import lead_list, lead_detail, lead_create, lead_update, lead_delete app_name = "lead" urlpatterns = [ path('', lead_list), path('<int:pk>', lead_detail), path('<int:pk>/update/', lead_update), path('<int:pk>/delete/', lead_delete), path('create/', lead_create) ]
- Add a delete and update button the lead_detail.html page
Test 24.1 Go to
<body> <a href="/leads"> Go back to leads</a> <hr /> <h1>This is the details of {{ lead.first_name }}</h1> <p>This persons age: {{ lead.age }} </p> <p>The agent responsible for this lead is : {{ lead.agent }}</p> <hr /> <a href="/leads/{{ lead.pk }}/update/">Update</a> <a href="/leads/{{ lead.pk }}/delete/">Delete</a> </body>
http://127.0.0.1:8000/leads/
, choose a lead to be deleted
-
Edit the leads/urls.py
: urlpatterns = [ path('', lead_list, name='lead-list'), path('<int:pk>/', lead_detail, name='lead-detail'), path('<int:pk>/update/', lead_update, name='lead-update'), path('<int:pk>/delete/', lead_delete, name='lead-delete'), path('create/', lead_create, name='lead-create') #i.e. path('create-a-lead/', lead_create, name='lead-create') ]
-
Change to the URL's name in leads/lead_detail.html: from this
<a href="/leads"> Go back..
-><a href="{% url 'leads:lead-list' %"> Go back..
<body> <a href="{% url 'leads:lead-list' %}">Go back to leads</a> <hr /> <h1>This is the details of {{ lead.first_name }}</h1> <p>This persons age: {{ lead.age }} </p> <p>The agent responsible for this lead is : {{ lead.agent }}</p> <hr /> <a href="{% url 'leads:lead-update' lead.pk %}">Update</a> <a href="{% url 'leads:lead-delete' lead.pk %}">Delete</a> </body>
-
Change to the URL's name in leads/lead_list.html: from this
<a href="/leads/create">Create..
-><a href="{% url 'leads:lead-create' %">Create..
<body> <a href="{% url 'leads:lead-create' %}">Create a new lead</a> <h1> This is all of our leads</h1> {% for lead in leads %} <div class="lead"> <a href="{% url 'leads:lead-detail' lead.pk %}"> {{ lead.first_name }} {{ lead.last_name }}</a>. Age: {{ lead.age }} </div> {% endfor %} </body>
-
Change to the URL's name in leads/lead_create.html:
<a href="/lead">Go back...
-><a href="{% url 'leads:lead-detail' %}">Go back...
<body> <a href="{% url 'leads:lead-list' %}"> Go back to leads</a> <hr /> <h1> Create a new lead</h1> <form method="post"> <!-- form method="post" action="/leads/another-url/"> --> {% csrf_token %} {{ form.as_p }} <button type="submit" >Submit</button> </form> </body>
-
Change to the URL's name in leads/lead_update.html:
<a href="/lead">Go back...
-><a href="{% url 'leads:lead-detail' %}">Go back...
<body> <a href="{% url 'leads:lead-detail' lead.pk %}">Go back to {{ lead.first_name }} {{ lead.last_name }} </a> <hr /> <h1>Update lead: {{ lead.first_name }} {{ lead.last_name }}</h1> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Submit</button> </form> </body>
Compiled in the branch of
ver-1.2
- Create base.html in crm/templates/
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>CRM</title> <style> .lead { padding-top: 10px; padding-bottom: 10px; padding-left: 6px; padding-right: 6px; margin-top: 10px; background-color: #f6f6f6; width: 100%; } </style> </head> <body> {% block content %} {% endblock content %} </body> </html>
- Update the leads/lead_list.html file
{% extends "base.html" %} {% block content %} <a href="{% url 'leads:lead-create' %}">Create a new lead</a> <hr /> <h1> This is all of our leads</h1> {% for lead in leads %} <div class="lead"> <a href="{% url 'leads:lead-detail' lead.pk %}"> {{ lead.first_name }} {{ lead.last_name }}</a>. Age: {{ lead.age }} </div> {% endfor %} {% endblock content %}
- Update the leads/lead_detail.html file
{% extends "base.html" %} {% block content %} <a href="{% url 'leads:lead-list' %}">Go back to leads</a> <hr /> <h1>This is the details of {{ lead.first_name }}</h1> <p>This persons age: {{ lead.age }} </p> <p>The agent responsible for this lead is : {{ lead.agent }}</p> <hr /> <a href="{% url 'leads:lead-update' lead.pk %}">Update</a> <a href="{% url 'leads:lead-delete' lead.pk %}">Delete</a> {% endblock content %}
- Update the leads/lead_update.html file
{% extends "base.html" %} {% block content %} <a href="{% url 'leads:lead-detail' lead.pk %}">Go back to {{ lead.first_name }} {{ lead.last_name }} </a> <hr /> <h1>Update lead: {{ lead.first_name }} {{ lead.last_name }}</h1> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Submit</button> </form> {% endblock content %}
- Update the leads/lead_create.html file
{% extends "base.html" %} {% block content %} <a href="{% url 'leads:lead-list' %}"> Go back to leads</a> <hr /> <h1> Create a new lead</h1> <form method="post"> <!-- form method="post" action="/leads/another-url/"> --> {% csrf_token %} {{ form.as_p }} <button type="submit" >Submit</button> </form> {% endblock content %}
- Update the leads/lead_list.html file
{% extends "base.html" %} {% block content %} <a href="{% url 'leads:lead-create' %}">Create a new lead</a> <hr /> <h1> This is all of our leads</h1> {% for lead in leads %} <div class="lead"> <a href="{% url 'leads:lead-detail' lead.pk %}"> {{ lead.first_name }} {{ lead.last_name }}</a>. Age: {{ lead.age }} </div> {% endfor %} {% endblock content %}
- Example to create ah html script file crm/templates/scripts.html and include it on the base.html
Edit the crm/templates/scripts.html<script> console.log("hello") </script>
- Include the scripts in base.html
Compiled in the branch of
: <body> {% block content %} {% endblock content %} {% include "scripts.html" %} </body>
ver-1.3
-
Go to https://v2.tailwindcss.com/docs/installation#using-tailwind-via-cdn and edit templates/base.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>CRM</title> <link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet"> </head> <body> <div class = "max-w-4xl mx-auto bg-gray-100"> {% block content %} {% endblock content %} </div> </body> </html>
-
Create the file crm/templates/navbar.html and add the navbar header from https://tailblocks.cc/
<header class="text-gray-600 body-font"> <div class="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center"> <a class="flex title-font font-medium items-center text-gray-900 mb-4 md:mb-0"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-10 h-10 text-white p-2 bg-indigo-500 rounded-full" viewBox="0 0 24 24"> <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"></path> </svg> <span class="ml-3 text-xl"> CRM </span> </a> <nav class="md:ml-auto flex flex-wrap items-center text-base justify-center"> <a href="{% url 'leads:lead-list' %}" class="mr-5 hover:text-gray-900">Leads</a> <a class="mr-5 hover:text-gray-900">Sign up</a> </nav> <a href="{% url 'leads:lead-list' %}" class="inline-flex items-center bg-gray-100 border-0 py-1 px-3 focus:outline-none hover:bg-gray-200 rounded text-base mt-4 md:mt-0">Login <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-1" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> </div> </header>
-
Update the templates/base.html file, including the navbar.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>CRM</title> <link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet"> </head> <body> <div class = "max-w-7xl mx-auto"> {% include 'navbar.html' %} {% block content %} {% endblock content %} </div> </body> </html>
Test 27.1 Go to
http://127.0.0.1:8000/leads/
-
Create the file templates/landing.html and add the code from HERO 2nd option at https://tailblocks.cc/
{% extends 'base.html' %} {% block content %} <section class="text-gray-600 body-font"> <div class="container mx-auto flex px-5 py-24 items-center justify-center flex-col"> <img class="lg:w-2/6 md:w-3/6 w-5/6 mb-10 object-cover object-center rounded" alt="hero" src="https://dummyimage.com/720x600"> <div class="text-center lg:w-2/3 w-full"> <h1 class="title-font sm:text-4xl text-3xl mb-4 font-medium text-gray-900"> CRM build with Django </h1> <p class="mb-8 leading-relaxed"> This CRM helps you manage your leads. </p> <div class="flex justify-center"> <button class="inline-flex text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg">Button</button> <button class="ml-4 inline-flex text-gray-700 bg-gray-100 border-0 py-2 px-6 focus:outline-none hover:bg-gray-200 rounded text-lg">Button</button> </div> </div> </div> </section> {% endblock content %}
-
Define the landing_page in leads/views.py
def landing_page(request): return render(request, "landing.html")
-
Create the paths in crm/urls.py
: from leads.views import landing_page urlpatterns = { : path('', landing_page, name='landing-page'), : ]
Test 27.2 Go to
http://127.0.0.1:8000
-
Format the leads/lead_list.html from tailwindcss (FEATURE, 4th option) inside the {% block content %}
{% extends "base.html" %} {% block content %} <section class="text-gray-600 body-font"> <div class="container px-5 py-24 mx-auto flex flex-wrap"> <!-- 1st script Added --> <div class="w-full mb-6 py-6 flex justify-between items-center border-b border-gray-200"> <div> <h1 clas="text-4xl text-gray-800">Lead</h1> </div> <div> <a class="text-gray-500 hover:text-blue-500" href="{% url 'leads:lead-create' %}">Create a new lead</a> </div> </div> <!-- End 1st script --> <div class="flex flex-wrap -m-4"> {% for lead in leads %} <div class="p-4 lg:w-1/2 md:w-full"> <div class="flex border-2 rounded-lg border-gray-200 border-opacity-50 p-8 sm:flex-row flex-col"> <div class="w-16 h-16 sm:mr-8 sm:mb-0 mb-4 inline-flex items-center justify-center rounded-full bg-indigo-100 text-indigo-500 flex-shrink-0"> <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-8 h-8" viewBox="0 0 24 24"> <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path> </svg> </div> <div class="flex-grow"> <h2 class="text-gray-900 text-lg title-font font-medium mb-3">{{ lead.first_name }} {{ lead.last_name }}</h2> <p class="leading-relaxed text-base">Blue bottle crucifix vinyl post-ironic four dollar toast vegan taxidermy. Gastropub indxgo juice poutine.</p> <a href="{% url 'leads:lead-detail' lead.pk %}" class="mt-3 text-indigo-500 inline-flex items-center">View this lead <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> </div> </div> </div> {% endfor %} </div> </div> </section> {% endblock content %}
Test 27.3 Go to
http://127.0.0.1:8000/leads/
-
Format the leads/lead_detail.html from tailwindcss (ECOMMERCE, 2nd option) inside the {% block content %}
{% extends "base.html" %} {% block content %} <section class="text-gray-600 body-font overflow-hidden"> <div class="container px-5 py-24 mx-auto"> <div class="lg:w-4/5 mx-auto flex flex-wrap"> <div class="lg:w-1/2 w-full lg:pr-10 lg:py-6 mb-6 lg:mb-0"> <h2 class="text-sm title-font text-gray-500 tracking-widest">Lead</h2> <h1 class="text-gray-900 text-3xl title-font font-medium mb-4">{{ lead.first_name }} {{ lead.last_name }}</h1> <div class="flex mb-4"> <a href="{% url 'leads:lead-detail' lead.pk %}" class="flex-grow text-indigo-500 border-b-2 border-indigo-500 py-2 text-lg px-1">Overview</a> <!-- interchaged --> <a class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Reviews</a> <!-- interchaged --> <a href="{% url 'leads:lead-update' lead.pk %}" class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Update Details</a> <!-- interchaged --> </div> <p class="leading-relaxed mb-4">Fam locavore kickstarter distillery. Mixtape chillwave tumeric sriracha taximy chia microdosing tilde DIY. XOXO fam inxigo juiceramps cornhole raw denim forage brooklyn. Everyday carry +1 seitan poutine tumeric. Gastropub blue bottle austin listicle pour-over, neutra jean.</p> <div class="flex border-t border-gray-200 py-2"> <span class="text-gray-500">Age</span> <span class="ml-auto text-gray-900">{{ lead.age }}</span> </div> <div class="flex border-t border-gray-200 py-2"> <span class="text-gray-500">Location</span> <span class="ml-auto text-gray-900">Random location</span> </div> <div class="flex border-t border-b mb-6 border-gray-200 py-2"> <span class="text-gray-500">Cellphone</span> <span class="ml-auto text-gray-900">+351244352432</span> </div> <div class="flex"> <!-- <span class="title-font font-medium text-2xl text-gray-900">$58.00</span> --> <button class="flex ml-auto text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded">Button</button> <button class="rounded-full w-10 h-10 bg-gray-200 p-0 border-0 inline-flex items-center justify-center text-gray-500 ml-4"> <svg fill="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-5 h-5" viewBox="0 0 24 24"> <path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"></path> </svg> </button> </div> </div> <img alt="ecommerce" class="lg:w-1/2 w-full lg:h-auto h-64 object-cover object-center rounded" src="https://dummyimage.com/400x400"> </div> </div> </section> <!-- <a href="{% url 'leads:lead-list' %">Go back to leads</a> <hr /> <h1>This is the details of {{ lead.first_name }}</h1> <p>This persons age: {{ lead.age }} </p> <p>The agent responsible for this lead is : {{ lead.agent }}</p> <hr /> <a href="{% url 'leads:lead-update' lead.pk %}">Update</a> <a href="{% url 'leads:lead-delete' lead.pk %}">Delete</a> --> {% endblock content %}
-
Format the leads/lead_update.html from tailwindcss (ECOMMERCE, 2nd option) inside the {% block content %}
{% extends "base.html" %} {% block content %} <section class="text-gray-600 body-font overflow-hidden"> <div class="container px-5 py-24 mx-auto"> <div class="lg:w-4/5 mx-auto flex flex-wrap"> <div class="lg:w-1/2 w-full lg:pr-10 lg:py-6 mb-6 lg:mb-0"> <h2 class="text-sm title-font text-gray-500 tracking-widest">Lead</h2> <h1 class="text-gray-900 text-3xl title-font font-medium mb-4">{{ lead.first_name }} {{ lead.last_name }}</h1> <div class="flex mb-4"> <a href="{% url 'leads:lead-detail' lead.pk %}" class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Overview</a> <!-- interchaged --> <a class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Reviews</a> <!-- interchaged --> <a href="{% url 'leads:lead-update' lead.pk %}" class="flex-grow text-indigo-500 border-b-2 border-indigo-500 py-2 text-lg px-1">Update Details</a> <!-- interchaged --> </div> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Submit</button> </form> <a href="{% url 'leads:lead-delete' lead.pk %}" class="w-1/2 mt-3 flex ml-auto text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded">Delete</a> </div> <img alt="ecommerce" class="lg:w-1/2 w-full lg:h-auto h-64 object-cover object-center rounded" src="https://dummyimage.com/400x400"> </div> </div> </section> <!-- <a href="{% url 'leads:lead-list' %">Go back to leads</a> <hr /> <h1>This is the details of {{ lead.first_name }}</h1> <p>This persons age: {{ lead.age }} </p> <p>The agent responsible for this lead is : {{ lead.agent }}</p> <hr /> <a href="{% url 'leads:lead-update' lead.pk %}">Update</a> <a href="{% url 'leads:lead-delete' lead.pk %}">Delete</a> <a href="{% url 'leads:lead-detail' lead.pk %}">Go back to {{ lead.first_name }} {{ lead.last_name }} </a> <hr /> <h1>Update lead: {{ lead.first_name }} {{ lead.last_name }}</h1> --> {% endblock content %}
Test 27.4 Go to
http://127.0.0.1:8000/leads/update
- Create a templates/leads/lead_delete.html
{% extends "base.html" %} {% block content %} <a href="{% url 'leads:lead-list' %}"> Go back to leads</a> <hr /> <h1>Are you sure you want to delete this lead?</h1> <form method="post"> <!-- form method="post" action="/leads/another-url/"> --> {% csrf_token %} {{ form.as_p }} <button type="submit" >Submit</button> </form> {% endblock content %}
- In leads/views.py, add class LandingPageView using
django.viwes.generic
from django.shortcuts import render, redirect, reverse from django.http import HttpResponse from django.views.generic import (TemplateView, ListView, DetailView, CreateView, DeleteView, UpdateView) from .models import Lead, Agent from .forms import LeadModelForm class LandingPageView(TemplateView): template_name = "landing.html" #def landing_page(request): # return render(request, "landing.html") class LeadListView(ListView): template_name = "leads/lead_list.html" queryset = Lead.objects.all() context_object_name = "leads" #def lead_list(request): # leads = Lead.objects.all() # context = { # "leads": leads # } # return render(request, "leads/lead_list.htnl) class LeadDetailView(DetailView): template_name = "leads/lead_detail.html" queryset = Lead.objects.all() context_object_name = "lead" #def lead_detail(request, pk): # lead = Lead.objects.get(id=pk) # context = { # "lead": lead # } # return render(request, "leads/lead_detail.html", context) class LeadCreateView(CreateView): template_name = "leads/lead_create.html" form_class = LeadModelForm def get_success_url(self): return reverse("leads:lead-list") #def lead_create(request): # form = LeadModelForm() # if request.method == "POST": # form = LeadModelForm(request.POST) # if form.is_valid(): # form.save() # return redirect("/leads") # context = { # "form": form # } # return render(request, "leads/lead_create.html", context) class LeadUpdateView(UpdateView): template_name = "leads/lead_update.html" queryset = Lead.objects.all() form_class = LeadModelForm def get_success_url(self): return reverse("leads:lead-list") #def lead_update(request, pk): # lead = Lead.objects.get(id=pk) # form = LeadModelForm(instance=lead) # if request.method == "POST": # form = LeadModelForm(request.POST, instance=lead) # if form.is_valid(): # form.save() # return redirect("/leads") # context = { # "form": form, # "lead": lead # } class LeadDeleteView(DeleteView): template_name = "leads/lead_delete.html" queryset = Lead.objects.all() def get_success_url(self): return reverse("leads:lead-list") #def lead_delete(request, pk): # lead = Lead.objects.get(id=pk) # lead.delete() # return redirect("/leads")
- Edit the crm/urls.py
from django.contrib import admin from django.urls import path, include from leads.views import LandingPageView, LeadListView urlpatterns = [ path('admin/', admin.site.urls), path('', LandingPageView.as_view(), name='landing_page'), path('leads/', include('leads.urls', namespace="leads")) ]
- Edit the leads/urls.py
Compiled in the branch of
from .views import (lead_list, lead_detail, lead_create, lead_update, lead_delete, LeadListView, LeadDetailView, LeadCreateView, LeadDeleteView, LeadUpdateView) app_name = "leads" urlpatterns = [ path('', LeadListView.as_view(), name='lead-list'), path('<int:pk>/', LeadDetailView.as_view(), name='lead-detail'), path('create/', LeadCreateView.as_view(), name='lead-create'), path('<int:pk>/update/', LeadUpdateView.as_view(), name='lead-update'), path('<int:pk>/delete/', LeadDeleteView.as_view(), name='lead-delete'), #path('', lead_list, name='lead-list'), #path('<int:pk>/', lead_detail, name='lead-detail'), #path('<int:pk>/update/', lead_update, name='lead-update'), #path('<int:pk>/delete/', lead_delete, name='lead-delete'), #path('create/', lead_create, name='lead-create') ]
ver-1.4
- Create the folder ./static and add the files main.js (console.log("hi") & style.css
- Configure the crm/settings.py
: STATIC_URL = '/static/' STATICFILES_DIRS = [ BASE_DIR/ "static" ] STATIC_ROOT = "static_root"
- Edit crm/urls.py and import the settings and static
Test: Go to
: from django.conf import settings from django.conf.urls.static import static : urlpatterns = [ path('admin/', admin.site.urls), path('', LandingPageView.as_view(), name='landing_page'), path('leads/', include('leads.urls', namespace="leads")) #static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) ] # access to static only if it's on debug mode if settings.DEBUG: urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
http:127.0.0.1:8000/static/main.js
console.log("hi")
- Create folders: css, images, js in crm/static and move the files style.css and main.js to those folders
- Add link to style.css in the templates/base.html
{% load static %} <!DOCTYPE html> : <link href = "{% static 'css/styles.css' %}" rel="stylesheet" />
- Call static/js/main.js from the templates/scripts.html file
Edit(delete content) o templates/scripts.htmlTest: Go to{% load static %} <script src="{% static 'js/main.js' %}"></script>
http://127.0.0.1:8000
F12 inspect and see the message in the terminal
- Edit the leads/views.py (more info check env/lib/python3.7/site-packages/django/core/init.py send_mail section)
from django.core.mail import send_mail : class LeadCreateView(CreateView): template_name = "leads/lead_create.html" form_class = LeadModelForm def get_success_url(self): return "/leads" def form_valid(self, form): send_mail( subject="A lead has been created", message="Go to the website to see the new lead", from_email="test@test.com", recipient_list=["test2@test.com"] ) return super(LeadCreateView, self).form_valid(form)
- Edit the email backend in crm/settings.py
Test: Create a lead in
: STATIC ROOT = "static_root' : AUTH_USER_MODEL = 'leads.User' EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
http:127.0.0.1:8000/leads/create
and check on the VS console the email that has been sent alering that a new lead has been created.
-
Create the folder
registration
in crm/templates/ and the file login.html & logout.html inside -
Edit the crm/templates/registration/login.html file
{% extends 'base.html' %} {% block content %} <form method="post"> {% csrf_token %} {{ form.as_p }} <button type='submit'>Login</button> </form> {% endblock content %}
-
Edit the crm/templates/registration/logout.html file
{% extends 'base.html' %} {% block content %} <h2> Thanks for visiting, you have been logged out </h2> {% endblock content %}
-
Import LoginView & LogoutView in crm/urls.py, more info in
env/lib/python3.7/site-packages/django/contrib/auth/views.py
: from django.contrib.auth.views import LoginView, LogoutView : urlpatterns = [ path('admin/, admin.site.urls), path('', LandingPageView.as_view(), name='landing-page'), path('leads/', include('leads.urls', name='leads'), path('login/', LoginView.as_view(), name='login'), path('logout/', LogoutView.as_view(), name='logout') ]
-
Configure redirect after login, go to crm/settings.py
: EMAIL_BACKEND = ... LOGIN_REDIRECT_URL = "/leads"
Test: Go to
http://127.0.0.1:8000/login/
and test with the superuser or a random user to see the success/error of login -
Configure the navbar after login, in crm/templates/navbar.html
<header class="text-gray-600 body-font"> <div class="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center"> <a class="flex title-font font-medium items-center text-gray-900 mb-4 md:mb-0"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-10 h-10 text-white p-2 bg-indigo-500 rounded-full" viewBox="0 0 24 24"> <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"></path> </svg> <span class="ml-3 text-xl"> CRM </span> </a> <nav class="md:ml-auto flex flex-wrap items-center text-base justify-center"> <a href="{% url 'leads:lead-list' %}" class="mr-5 hover:text-gray-900">Leads</a> {% if not request.user.is_authenticated %} <a class="mr-5 hover:text-gray-900">Sign up</a> {% endif %} </nav> {% if request.user.is_authenticated %} Logged in as: {{ request.user.username }} <a href="#" class="ml-3 inline-flex items-center bg-gray-100 border-0 py-1 px-3 focus:outline-none hover:bg-gray-200 rounded text-base mt-4 md:mt-0">Logout <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-1" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> {% else %} <a href="{% url 'leads:lead-list' %}" class="inline-flex items-center bg-gray-100 border-0 py-1 px-3 focus:outline-none hover:bg-gray-200 rounded text-base mt-4 md:mt-0">Login <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-1" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> {% endif %} </div> </header>
-
Create the signup view in leads/views.py
: from django.contrib.auth.forms import UserCreationForm : class SignupView(CreateView): template_name = "registration/signup.html" form_calss = UserCreationForm def get_success_url(self): return reverse("login")
-
Create the templates/registration/signup.html file
{% extends 'base.html' %} {% block content %} <form method="post"> {% csrf_token %} {{ form.as_p }} <button type='submit'>Signup</button> </form> {% endblock content %}
-
Edit the crm/urls.py
: from leads.views import LandingPageView, SignupView : urlpatterns = [ path('admin/, admin.site.urls), path('', LandingPageView.as_view(), name='landing-page'), path('leads/', include('leads.urls', name='leads'), path('signup/', SignupView.as_view(), name='signup'), path('login/', LoginView.as_view(), name='login'), path('logout/', LogoutView.as_view(), name='logout') ]
-
Configure the navbar with the signup templates/navbar.html
<header class="text-gray-600 body-font"> <div class="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center"> <a class="flex title-font font-medium items-center text-gray-900 mb-4 md:mb-0"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-10 h-10 text-white p-2 bg-indigo-500 rounded-full" viewBox="0 0 24 24"> <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"></path> </svg> <span class="ml-3 text-xl"> CRM </span> </a> <nav class="md:ml-auto flex flex-wrap items-center text-base justify-center"> <a href="{% url 'leads:lead-list' %}" class="mr-5 hover:text-gray-900">Leads</a> {% if not request.user.is_authenticated %} <a href="{% url 'signup' %}" class="mr-5 hover:text-gray-900">Sign up</a> {% endif %} </nav> {% if request.user.is_authenticated %} Logged in as: {{ request.user.username }} <a href="{% url 'logout' %}" class="ml-3 inline-flex items-center bg-gray-100 border-0 py-1 px-3 focus:outline-none hover:bg-gray-200 rounded text-base mt-4 md:mt-0">Logout <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-1" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> {% else %} <a href="{% url 'login' %}" class="inline-flex items-center bg-gray-100 border-0 py-1 px-3 focus:outline-none hover:bg-gray-200 rounded text-base mt-4 md:mt-0">Login <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-1" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> {% endif %} </div> </header>
-
Create the own userform: CustomUserCreationForm in leads/forms.py
from django import forms from django.contrib.auth import get_user_model from django.contrib.auth.forms import UserCreationForm, UsernameField from .models import Lead User = get_user_model() class CustomUserCreationForm(UserCreationForm): class Meta: model = User fields = ("username",) field_classes = {'username': UsernameField}
-
Edit the leads/views.py
#from django.contrib.auth.forms import UserCreationForm from .forms import LeadForm, LeadModelForm, CustomUserCreationForm class SignupView(CreateView): template_name = "registration/signup.html" form_class = CustomUserCreationForm def get_success_url(self): return reverse("login")
Test: Go to
http://127.0.0.1:8000/signup/
and create a new user, verify the user inhttp://127.0.0.1:8000/login/
-
The test python files always start with the string "test_name.py"
-
Test the landing page, edit leads/templatees/leads/tests.py
from django.test import TestCase from django.shortcuts import reverse class LandingPageTest(TestCase): def test_status_code(self): # TODO some sort of test response = self.client.get(reverse("landing-page")) print(response.content) self.asserEqual(response.status_code, 200) #check status self.asserTemplateUser(response, "landing.html") #check response of page def test_template_name(self): response = self.client.get(reverse("landing-page")) self.assertTemplateUsed(response, "landing.html") def test_get(self): # TODO some sort of test response = self.client.get(reverse("landing-page")) self.asserEqual(response.status_code, 200) self.asserTemplateUser(response, "landing.html")
Test in console: python manage.py test
-
Create a tests folder in crm/leads, to run the tests' files {test_views,test_forms), also add the init.py
from django.test import TestCase from django.shortcuts import reverse class LandingPageTest(TestCase): def test_get(self): response = self.client.get(reverse("landing-page")) self.asserEqual(response.status_code, 200) self.asserTemplateUser(response, "landing.html")
Restrict users to be only the leads they created
-
Edit leads/views.py, pass the LoginRequiredMixin to the models that require it
from django.contrib.auth.mixins import LoginRequiredMixin class LeadListView(LoginRequiredMixin, ListView): : class LeadDetailView(LoginRequiredMixin, DetailView): : class LeadCreateView(LoginRequiredMixin, CreateView): : class LeadUpdateView(LoginRequiredMixin, UpdateView): : class LeadDeleteView(LoginRequiredMixin, DeleteView): :
Go to
http://127.0.0.1/leads
and verify the restriction with the error (you must be logged out or in incognito mode) -
Modify the redirection so it ca redirect to the login site, go to crm/settings.py
: LOGIN_REDIRECT_URL = "/leads" LOGIN_URL = "/login"
-
Create a model for the user so that the agent will inherit the userprofile properties in crm/leads/models.py
class UserProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) def __str__(self): return self.user.username class Agent(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) organization = models.ForeignKey(UserProfile, on_delete=models.CASCADE) def __str__(self): return self.user.email
-
Add the userprofile tab in the database of the admin website, edit leads/admin.py
from django.contrib import admin from .models import User, Lead, Agent, UserProfile admin.site.register(User) admin.site.register(UserProfile) admin.site.register(Lead) admin.site.register(Agent)
-
To test, delete the crm/db.sqlite3 database, migrate and create again the superuser
python manage.py migrate python manage.py createsuperuser user: jose password: 1 python manage.py makemigrations 1 2 python manage.py migrate python manage.py runserver
Note: We wouldn't want to create a user profile for the new users because that process ought be automatic, so the triggering of events is handled by signals in Django
-
Edit leads/models.py
from django.db.models.signals import post_save def post_user_created_signal(sender, instance, created, **kwargs): print(instance, created) #created boolena T/F if the user was or not created post_save.connect(post_user_created_signal, sender=User)
Test in
http://127.0.0.1:8000/admin/leads/user/
, select a user and then in his profile, click saveFor instance the above script depicts a process when we push the save buttom of a user through the admin site, after clicking the save command, the event post_user_created_signal is triggered and shows the name of the user(instance) in the VS terminal
-
Configure the creation of a userprofile after the user was created, in leads/models.py
def post_user_created_signal(sender, instance, created, **kwargs): if created: UserProfile.objects.create(user=instance) :
-
Create the new app in crm folder:
python manage.py startapp agents
-
Add the agent app in crm/settings.py
INSTALLED_APPS = [ : 'leads', 'agents' ]
-
Create crm/agents/urls.py and edit
from django.urls import path from .views import AgentListView, AgentCreateView app_name = 'agents' urlpatterns = [ path('', AgentListView.as_view(), name='agent-list'), path('create/', AgentCreateView.as_view(), name='agent-create') ]
-
Add the app in crm/urls.py
: urlpatterns = [ : path('agents/', include('agents.urls', namespace='agents')), : ]
-
Edit the agents/views.py file
from django.views import generic from django.contrib.auth.mixins import LoginRequiredMixin from leads.models import Agent from django.shortcuts import reverse from .forms import AgentModelForm class AgentListView(LoginRequiredMixin, generic.ListView): template_name = "agents/agent_list.html" def get_queryset(self): return Agent.objects.all() class AgentCreateView(LoginRequiredMixin, generic.CreateView): template_name = "agents/agent_create.html" form_class = AgentModelForm def get_success_url(self): return reverse("agents:agent-list") def form_valid(self, form): agent = form.save(commit=False) agent.organization = self.request.user.userprofile #agents have a organiz property, Check leads/models.py agent.save() #agent is save in the organization return super(AgentCreateView, self).form_valid(form)
-
Create the templates folder inside the agents app (crm/agents/templates) and then the folder crm/agents/templates/agents.
Inside agents/templates/agents/ create the agent_list.html file and edit it{% extends "base.html" %} {% block content %} <section class="text-gray-600 body-font"> <div class="container px-5 py-24 mx-auto flex flex-wrap"> <div class="w-full mb-6 py-6 flex justify-between items-center border-b border-gray-200"> <div> <h1 class="text-4xl text-gray-800">Agents</h1> </div> <div> <a class="text-gray-500 hover:text-blue-500" href="{% url 'agents:agent-create' %}">Create a new agent</a> </div> </div> <div class="flex flex-wrap -m-4"> {% for agent in object_list %} <div class="p-4 lg:w-1/2 md:w-full"> <div class="flex border-2 rounded-lg border-gray-200 border-opacity-50 p-8 sm:flex-row flex-col"> <div class="w-16 h-16 sm:mr-8 sm:mb-0 mb-4 inline-flex items-center justify-center rounded-full bg-indigo-100 text-indigo-500 flex-shrink-0"> <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-8 h-8" viewBox="0 0 24 24"> <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path> </svg> </div> <div class="flex-grow"> <h2 class="text-gray-900 text-lg title-font font-medium mb-3">{{ agent.user.username }}</h2> <p class="leading-relaxed text-base">Blue bottle crucifix vinyl post-ironic four dollar toast vegan taxidermy. Gastropub indxgo juice poutine.</p> <a href="{% url 'agents:agent-detail' agent.pk %}" class="mt-3 text-indigo-500 inline-flex items-center">View agent <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> </div> </div> </div> {% endfor %} </div> </div> </section> {% endblock content %}
-
Create crm/agents/forms.py
from django import forms from leads.models import Agent class AgentModelForm(forms.ModelForm): class Meta: model = Agent fields = ( 'user', )
-
Create the agents/templates/agents/agent_create.html
{% extends "base.html" %} {% block content %} <a href="{% url 'agents:agent-list' %}"> Go back to agents</a> <hr /> <h1> Create a new agent</h1> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit" >Submit</button> </form> {% endblock content %}
Test: Go to
http://127.0.0.1:8000/agents/
and click on Create a new agent
- Create the AgentDetailView in agents/views.py
: class AgentDetailView(LoginRequiredMixin, generic.DetailView): template_name = "agents/agent_detail.html" def get_queryset(self): return Agent.objects.all
- Edit the agents/urls.py
from django.urls import path from .views import AgentListView, AgentCreateView, AgentDetailView app_name = 'agents' urlpatterns=[ path('', AgentListView.as_view(), name='agent-list'), path('<int:pk>/', AgentDetailView.as_view(), name='agent-detail'), path('create/', AgentCreateView.as_view(), name='agent-create'), ]
- Create agent_detail.html inside templates/agents
{% extends "base.html" %} {% block content %} <section class="text-gray-600 body-font overflow-hidden"> <div class="container px-5 py-24 mx-auto"> <div class="lg:w-4/5 mx-auto flex flex-wrap"> <div class="lg:w-1/2 w-full lg:pr-10 lg:py-6 mb-6 lg:mb-0"> <h2 class="text-sm title-font text-gray-500 tracking-widest">AGENT</h2> <h1 class="text-gray-900 text-3xl title-font font-medium mb-4">{{ agent.user.username }}</h1> <div class="flex mb-4"> <a href="{% url 'agents:agent-detail' agent.pk %}" class="flex-grow text-indigo-500 border-b-2 border-indigo-500 py-2 text-lg px-1">Overview</a> <a class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Reviews</a> <a href="#" class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Update Details</a> </div> <p class="leading-relaxed mb-4">Fam locavore kickstarter distillery. Mixtape chillwave tumeric sriracha taximy chia microdosing tilde DIY. XOXO fam inxigo juiceramps cornhole raw denim forage brooklyn. Everyday carry +1 seitan poutine tumeric. Gastropub blue bottle austin listicle pour-over, neutra jean.</p> <div class="flex border-t border-gray-200 py-2"> <span class="text-gray-500">Age</span> <span class="ml-auto text-gray-900">{{ lead.age }}</span> </div> <div class="flex border-t border-gray-200 py-2"> <span class="text-gray-500">Location</span> <span class="ml-auto text-gray-900">Random location</span> </div> <div class="flex border-t border-b mb-6 border-gray-200 py-2"> <span class="text-gray-500">Cellphone</span> <span class="ml-auto text-gray-900">+351244352432</span> </div> <div class="flex"> <!-- <span class="title-font font-medium text-2xl text-gray-900">$58.00</span> --> <button class="flex ml-auto text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded">Button</button> <button class="rounded-full w-10 h-10 bg-gray-200 p-0 border-0 inline-flex items-center justify-center text-gray-500 ml-4"> <svg fill="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-5 h-5" viewBox="0 0 24 24"> <path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"></path> </svg> </button> </div> </div> <img alt="ecommerce" class="lg:w-1/2 w-full lg:h-auto h-64 object-cover object-center rounded" src="https://dummyimage.com/400x400"> </div> </div> </section> {% endblock content %}
- Edit the agents/views.py file to add the agentdetailview class
class AgentDetailView(LoginRequiredMixin, generic.DetailView): template_name = "agents/agent_detail.html" context_object_name = "agent" def get_queryset(self): return Agent.objects.all() ```
- Update the navbar.html to display the correct path of agents
<header class="text-gray-600 body-font"> <div class="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center"> <a class="flex title-font font-medium items-center text-gray-900 mb-4 md:mb-0"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-10 h-10 text-white p-2 bg-indigo-500 rounded-full" viewBox="0 0 24 24"> <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"></path> </svg> <span class="ml-3 text-xl"> CRM </span> </a> <nav class="md:ml-auto flex flex-wrap items-center text-base justify-center"> {% if not request.user.is_authenticated %} <a href="{%url 'signup' %}" class="mr-5 hover:text-gray-900">Sign up</a> {% else %} <a href="{% url 'agents:agent-list' %}" class="mr-5 hover:text-gray-900">Agents</a> <a href="{% url 'leads:lead-list' %}" class="mr-5 hover:text-gray-900">Leads</a> {% endif %} </nav> {% if request.user.is_authenticated %} Logged in as: {{ request.user.username }} <a href="{% url 'logout' %}" class="ml-3 inline-flex items-center bg-gray-100 border-0 py-1 px-3 focus:outline-none hover:bg-gray-200 rounded text-base mt-4 md:mt-0">Logout <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-1" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> {% else %} <a href="{% url 'login' %}" class="inline-flex items-center bg-gray-100 border-0 py-1 px-3 focus:outline-none hover:bg-gray-200 rounded text-base mt-4 md:mt-0">Login <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-1" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> {% endif %} </div> </header>
- Add the updateview & deleteview in agents/views.py
: class AgentUpdateView(LoginRequiredMixin, generic.UpdateView): template_name = "agents/agent_update.html" form_class = AgentModelForm def get_success_url(self): return reverse("agents:agent-list") def get_queryset(self): return Agent.objects.all() class AgentDeleteView(LoginRequiredMixin, generic.DeleteView): template_name = "agents/agent_delete.html" context_object_name = "agent" def get_success_url(self): return reverse("agents:agent-list") def get_queryset(self): return Agent.objects.all()
- Update the agents/urls.py and add de updateview & deleteview
from django.urls import path from .views import (AgentListView, AgentCreateView, AgentDetailView, AgentUpdateView, AgentDeleteView) app_name = 'agents' urlpatterns = [ path('', AgentListView.as_view(), name='agent-list'), path('<int:pk>/', AgentDetailView.as_view(), name='agent-detail'), path('<int:pk>/update/', AgentUpdateView.as_view(), name='agent-update'), path('<int:pk>/delete/', AgentDeleteView.as_view(), name='agent-delete'), path('create/', AgentCreateView.as_view(), name='agent-create'). ]
- Create the agents/templates/agents/agent_update.html
{% extends "base.html" %} {% block content %} <section class="text-gray-600 body-font overflow-hidden"> <div class="container px-5 py-24 mx-auto"> <div class="lg:w-4/5 mx-auto flex flex-wrap"> <div class="lg:w-1/2 w-full lg:pr-10 lg:py-6 mb-6 lg:mb-0"> <h2 class="text-sm title-font text-gray-500 tracking-widest">AGENT</h2> <h1 class="text-gray-900 text-3xl title-font font-medium mb-4">{{ agent.user.username }}</h1> <div class="flex mb-4"> <a href="{% url 'agents:agent-detail' agent.pk %}" class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Overview</a> <!-- interchaged --> <a class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Reviews</a> <!-- interchaged --> <a href="{% url 'agents:agent-update' agent.pk %}" class="flex-grow text-indigo-500 border-b-2 border-indigo-500 py-2 text-lg px-1">Update Details</a> <!-- interchaged --> </div> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Submit</button> </form> <a href="{% url 'agents:agent-delete' agent.pk %}" class="w-1/2 mt-3 flex ml-auto text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded">Delete</a> </div> <img alt="ecommerce" class="lg:w-1/2 w-full lg:h-auto h-64 object-cover object-center rounded" src="https://dummyimage.com/400x400"> </div> </div> </section> {% endblock content %}
- Create the templates/leads/agent_delete.html
Test: Go to
{% extends "base.html" %} {% block content %} <a href="{% url 'agents:agent-list' %}"> Go back to agents</a> <hr /> <h1>Are you sure you want to delete this agent?</h1> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit" >Submit</button> </form> {% endblock content %}
http://127.0.0.1:8000/agents
and create/delete/update an agent, checkver-1.5
-
Edit the agents/views.py file
: class AgentListView(LoginRequiredMixin, generic.ListView): template_name = "agents/agent_list.html" def get_queryset(self): organization = self.request.user.userprofile return Agent.objects.filter(organization=organization) #Changed this return Agent.objects.all()
Test - Correctly filtering agents for the users: Go to
http://127.0.0.1:8000/admin/leads/agent
and add an AGENT (there must be two users already created under different categories: organization and without orgnization or simply users)
Assign to the Agent:
User: Test1
Organization: Test2
Now login to another user, like Jose, check inhttp://127.0.0.1:8000/agents
, you should only see your agent(s), not the ones recently assigned to different users or organizations! -
Extend the filtering functionality where there is a queryset (where something is retrieved back to the client), in agents/views.py
from django.views import generic from django.contrib.auth.mixins import LoginRequiredMixin from leads.models import Agent from django.shortcuts import reverse from .forms import AgentModelForm class AgentListView(LoginRequiredMixin, generic.ListView): template_name = "agents/agent_list.html" def get_queryset(self): organization = self.request.user.userprofile return Agent.objects.filter(organization=organization) class AgentCreateView(LoginRequiredMixin, generic.CreateView): template_name = "agents/agent_create.html" form_class = AgentModelForm def get_success_url(self): return reverse("agents:agent-list") def form_valid(self, form): agent = form.save(commit=False) agent.organization = self.request.user.userprofile #agents have a organiz property, Check leads/models.py agent.save() #agent is save in the organization return super(AgentcreateView, self).form_valid(form) class AgentDetailView(LoginRequiredMixin, generic.DetailView): template_name = "agents/agent_detail.html" context_object_name = "agent" def get_queryset(self): organization = self.request.user.userprofile return Agent.objects.filter(organization=organization) class AgentUpdateView(LoginRequiredMixin, generic.UpdateView): template_name = "agents/agent_update.html" form_class = AgentModelForm def get_success_url(self): return reverse("agents:agent-list") def get_queryset(self): organization = self.request.user.userprofile return Agent.objects.filter(organization=organization) class AgentDeleteView(LoginRequiredMixin, generic.DeleteView): template_name = "agents/agent_delete.html" context_object_name = "agent" def get_success_url(self): return reverse("agents:agent-list") #here is even more important because we don't users to delete agents that belong to other users def get_queryset(self): organization = self.request.user.userprofile return Agent.objects.filter(organization=organization)
- Create the type of users in crm/leads/models.py
from django.db import models from djangodb.models.signals import post_save from django.contrib.auth.models import AbstractUser class User(AbstractUser): is_organizer = models.BooleanField(default=True) is_agent = models.BooleanField(default=False)
Verify: Go topython manage.py makemigrations python manage.py migrate python manage.py runserver
http://127.0.0.1:8000/admin/leads/user/
choose a user and check the options down below for organizer(set to True as default) and agent.Only users set to be organizers can create and see all agents available
Users which are agents can only see the agent which is assigned to them.
Sometimes there might be an error in makemigrations, so either delete the database: dbsqlite3, or delete the init files: 0001_initial.py, etc.
>In this case, organizers can see the correct left tabs: Agents & Leads, and agents can see only Leads<br>
-
Edit the registration/navbar.html file
: {% if not request.user.is_authenticated %} <a href="{% url 'signup' %}" class="mr-5 hover:text-gra-900">Signup</a> {% else %} {% if request.user.is_organizer %} <a href="{% url 'agents:agent-list' %}" class="mr-5 hover:text-gra-900">Agents</a> {% endif %} <a href="{% url 'leads:lead-list' %}" class="mr-5 hover:text-gra-900">Leads</a> {% endif %} <!-- {% if not request.user.is_authenticated %} <a href="{% url 'signup' %}" class="mr-5 hover:text-gra-900">Signup</a> {% else %} <a href="{% url 'agents:agent-list' %}" class="mr-5 hover:text-gra-900">Agents</a> <a href="{% url 'leads:lead-list' %}" class="mr-5 hover:text-gra-900">Leadss</a> {% endif %} --> :
Test: create two users and configure one to is_organizer, the other to be
is_agent
(name this user as agent to be distringuishable).
Now procees to login with the agent user and verify that there is no agent tab in the right side of navbar. -
Restrict agents to hardcorde the url
http://127.0.0.1:8000/agents
, create the agents/templates/mixins.py to restrict to login and restrict see other agents if is not a organizerfrom django.contrib.auth.mixins import AccessMixin from django.shortcuts import redirect class OrganizerAndLoginRequiredMixin(AccessMixin): """Verifiy that the current user is authenticated and is an organizer""" def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated or not request.user.is_organizer: return redirect("leads:lead-list") return super().dispatch(request, *args, **kwargs)
-
Import the custom mixins: OrganizerAndLoginRequiredMixin (custom restriction for login and view of other agents) in agents/views.py
from django.views import generic from django.contrib.auth.mixins import LoginRequiredMixin from leads.models import Agent from django.shortcuts import reverse from .forms import AgentModelForm from .mixins import OrganizerAndLoginRequiredMixin class AgentListView(OrganizerAndLoginRequiredMixin, generic.ListView): template_name = "agents/agent_list.html" def get_queryset(self): organization = self.request.user.userprofile return Agent.objects.filter(organization=organization) class AgentCreateView(OrganizerAndLoginRequiredMixin, generic.CreateView): template_name = "agents/agent_create.html" form_class = AgentModelForm def get_success_url(self): return reverse("agents:agent-list") def form_valid(self, form): agent = form.save(commit=False) agent.organization = self.request.user.userprofile #agents have a organiz property, Check leads/models.py agent.save() #agent is save in the organization return super(AgentcreateView, self).form_valid(form) class AgentDetailView(LOrganizerAndLoginRequiredMixin, generic.DetailView): template_name = "agents/agent_detail.html" context_object_name = "agent" def get_queryset(self): organization = self.request.user.userprofile return Agent.objects.filter(organization=organization) class AgentUpdateView(OrganizerAndLoginRequiredMixin, generic.UpdateView): template_name = "agents/agent_update.html" form_class = AgentModelForm def get_success_url(self): return reverse("agents:agent-list") def get_queryset(self): organization = self.request.user.userprofile return Agent.objects.filter(organization=organization) class AgentDeleteView(OrganizerAndLoginRequiredMixin, generic.DeleteView): template_name = "agents/agent_delete.html" context_object_name = "agent" def get_success_url(self): return reverse("agents:agent-list") #here is even more important because we don't users to delete agents that belong to other users def get_queryset(self): organization = self.request.user.userprofile return Agent.objects.filter(organization=organization)
-
Restrict also in leads/views.py regarding leadcreateview,
: from agents.mixins import OrganizerAndLoginRequiredMixin : class LeadCreateView(OrganizerAndLoginRequiredMixin, CreateView): : class LeadUpdateView(OrganizerAndLoginRequiredMixin, UpdateView): : class LeadDeleteView(OrganizerAndLoginRequiredMixin, DeleteView):
-
Show
Create a new lead
to only organizers in the templates/leads/lead_list.html: {% if request.user.is_organizer %} <div> <a class="text-gray-500 hover:text-blue-500" href="{% url 'leads:lead-create' %}">Create a new lead</a> </div> {% endif %} <!-- <div> <a class="text-gray-500 hover:text-blue-500" href="{% url 'leads:lead-create' %}">Create a new lead</a> </div> --> :
Test: Login as an agent user, go to
http://127.0.0.1:8000/leads
, check the tab if available, and hop over to ``http://127.0.0.1:8000/leads/create`, the sites should redirect tohttp://127.0.0.1:8000/leads/
- Showing the leads to be seen by the agents that they belong to
Initially when leads are created, they shouldnt have set up an agent, so their agent will be null.
Go to ./leads/models.py and editclass Lead(models.Model): first_name = models.CharField(max_length=20) last_name = models.CharField(max_length=20) age = models.IntegerFiled(default=0) organization = models.ForeignKey(UserProfile, on_delete=models.CASCADE)# add the userprofile or organization that a Lead belongs agent = models.ForeignKey("Agent", null=True, blank=True, on_delete=models.SET_NULL) # so when the agent is deleted, the lead wont be deleted and will just remain an a lead without agent def __str__(self): return f"{self.first_name} {self.last_name}"
Leads filtered by the user of the agent (matchs the user of the lead: self.reques.user)python manage.py makemigrations python manage.py migrate 1 exit python manage.py runserver
- Edit the leads/views.py to configure the leads querysets so we end up restricting the shown of the leads based on the assigned agents
from django.shortcuts import render, redirect, reverse from django.http import HttpResponse from django.views.generic import (TemplateView, ListView, DetailView, CreateView, DeleteView, UpdateView) from .models import Lead, Agent from .forms import LeadModelForm class LandingPageView(TemplateView): template_name = "landing.html" class LeadListView(LoginRequiredMixin, generic.ListView): template_name = "leads/lead_list.html" context_object_name = "leads" def get_queryset(self): user = self.request.user if user.is_organizer: queryset = Lead.objects.filter(organization = user.userprofile) else: queryset = Lead.objects.filter(organization=user.agent.organization) queryset = queryset.filter(agent__user=user) return queryset class LeadDetailView(LoginRequiredMixin, generic.DetailView): template_name = "leads/lead_detail.html" context_object_name = "lead" def get_queryset(self): user = self.request.user # initial queryset of leads for the entrie organization if user.is_organizer: queryset = Lead.objects.filter(organization=user.userprofile) else: queryset = Lead.objects.filter(organization=user.agent.organization) #filter for the agent that is logged in queryset = queryset.filter(agent__user=user) return queryset class LeadCreateView(OrganizerAndLoginRequiredMixin, generic.CreateView): template_name = "leads/lead_create.html" form_class = LeadModelForm def get_success_url(self): #return "/leads" return reverse("leads:lead-list") def form_valid(self, form): lead = form.save(commit=False) lead.organization = self.request.user.userprofile lead.save() send_mail( subject="A lead has been created", message="Go to the website to see the new lead", from_email="test@test.com", recipient_list=["test2@test.com"] ) return super(LeadCreateView, self).form_valid(form) class LeadUpdateView(OrganizerAndLoginRequiredMixin, UpdateView): template_name = "leads/lead_update.html" form_class = LeadModelForm def get_queryset(self): user = self.request.user # initial queryset of leads for the entrie organization return Lead.objects.filter(organization=user.userprofile) def get_success_url(self): return reverse("leads:lead-list") class LeadDeleteView(OrganizerAndLoginRequiredMixin, DeleteView): template_name = "leads/lead_delete.html" def get_success_url(self): return reverse("leads:lead-list") def get_queryset(self): user = self.request.user return Lead.objects.filter(organization=user.userprofile)
- Modify agents/forms.py, so that when the agent is created, a user is immediately created as well.
from django import forms from django.contrib.auth import get_user_model from django.contrib.auth.forms import UserCreationForm User = get_user_model() class AgentModelForm(forms.ModelForm): class Meta: model = User fields = ( 'email', 'username', 'first_name', 'last_name' )
- Edit agents/views.py, to create an agent user
Test: Sign in like Jose and go to
: from django.core.mail import send_mail : class AgentCreateView(OrganizerAndLoginRequiredMixin, generic.CreateView): template_name = "agents/agent_create.html" form_class = AgentModelForm def get_success_url(self): return reverse("agents:agent-list") def form_valid(self, form): #create the user type agent user = form.save(commit=False) user.is_agent = True user.is_organizer = False user.save() # create the agent for that user Agent.objects.create( user=user, organization=self.request.user.userprofile ) send_mail subject="You are invited to be an agent", message="You were added as an agent on CRM. Please come login to stat working.", from_email="admin@test.com" recipient_list=[user.email] return super(AgentCreateView, self).form_valid(form)
http://127.0.0.1:8000/agents/create
, enter the details and submit, check the agent just created and the email on the terminal.
Also go to the admin website and check the user, it actually doesn't have a password so set it up like below, edit agents/views.pyimport random class AgentCreateView(OrganizerAndLoginRequiredMixin, generic.CreateView): template_name = "agents/agent_create.html" form_class = AgentModelForm def get_success_url(self): return reverse("agents:agent-list") def form_valid(self, form): #create the user type agent user = form.save(commit=False) user.is_agent = True user.is_organizer = False user.set_password(f"{random.randint(0, 100000)}") #ADDED, need to reset pass :
-
Edit the templates/registration/login.html/
{% extends 'base.html' %} {% block content %} <form method = "post"> {% csrf_token %} {{ form.as_p }} <button type = 'submit'>Login</button> <hr /> <a href='{% url 'reset-password' %}'>Forgot password?</a> </form> {% endblock content %}
-
Create in templates/registration/ the html files: password_reset_done.html, password_reset_email.html, password_reset_form.html, password_reset_confirm.html
-
Edit and add the class in crm/urls.py
from django.contrib.auth.views import ( LoginView, LogoutView, PasswordResetView, PasswordResetDoneView, PasswordResetConfirmView, PasswordResetCompleteView, : urlpatterns = [ path('admin/', admin.site.urls), path('', LandingPageView.as_view(), name='landing-page'), path('leads/', include('leads.urls', namespace="leads")), path('agents/', include('agents.urls', namespace='agents')), path('signup/', SignupView.as_view(), name='signup'), path('login/', LoginView.as_view(), name='login'), path('logout/', LogoutView.as_view(), name='logout'), path('reset-password/', PasswordResetView.as_view(), name='reset-password'), path('password-reset-done/', PasswordResetDoneView.as_view(), name='password_reset_done'), path('password-reset-confirm/<uidb64>/<token>/', PasswordResetConfirmView.as_view(), name='password_reset_confirm'), path('password-reset-complete/', PasswordResetCompleteView.as_view(), name='password_reset_complete'), ]
-
Edit the password_reset_form.html in crm/templates/registration/
{% extends 'base.html' %} {% block content %} <form method="post"> {% csrf_token %} {{ form.as_p }} <button type = 'submit'>Reset password</button> <hr /> <a href='{% url 'login' %}'>Already have an account?</a> </form> {% endblock content %}
-
Edit the password_reset_confirm.html in crm/templates/registration/
{% extends 'base.html' %} {% block content %} <h1> Enter your new password</h1> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type = 'submit'>Confirm new password</button> </form> {% endblock content %}
-
Edit the password_reset_done.html in crm/templates/registration/
{% extends "base.html" %} {% block content %} <h1>We have sent you an email to confirm your password reset</h1> {% endblock content %}
-
Edit the password_reset_email.html in crm/templates/registration/
You've requested to reset your password Please go to the following URL to enter your new password: {{ protocol }}://{{ domain }}/password-reset-confirm/{{ uid }}/{{ token }}/
-
Edit the password_reset_complete.html in crm/templates/registration/
{% extends 'base.html' %} {% block content %} <h1>Password reset complete</h1> <p>You have successfully reset your password. Click <a href="{% url 'login' %}"> here to login</a> </p> {% endblock content %}
Test: Grab a user and its email already created, go to
http://127.0.0.1:8000/login
, choose Forgot password, and then fill the email.
Check the link of the email sent in the terminal, copy the link into the browser and reset the email. Login with the new user's password.
- Edit the ./leads/views.py
class LeadListView(ListView): template_name = "leads/lead_list.html" context_object_name = "leads" def get_queryset(self): user = self.reques.user # initial queryset of leads for the entrie organization if user.is_organizer: queryset = Lead.objects.filter(organization=user.userprofile, agent__isnull=False) else: queryset = Lead.objects.filter(organization=user.agent.organization, agent__isnull=False) #filter for the agent that is logged in queryset = queryset.filet(agent__user=user) return queryset def get_context_data(self, **kwargs): context = super(LeadListView, self).get_context_data(**kwargs) user = self.request.user if user.is_organizer: queryset = Lead.objects.filter(organization=user.userprofile, agent__isnull=True) context.update({ "unassigned_leads": queryset }) return context
- Update the lead_list.html of leads/templates/leads/
{% extends "base.html" %} {% block content %} <section class="text-gray-600 body-font"> <div class="container px-5 py-24 mx-auto flex flex-wrap"> <div class="w-full mb-6 py-6 flex justify-between items-center border-b border-gray-200"> <div> <h1 class="text-4xl text-gray-800">Lead</h1> </div> {% if request.user.is_organizer %} <div> <a class="text-gray-500 hover:text-blue-500" href="{% url 'leads:lead-create' %}">Create a new lead</a> </div> {% endif %} </div> <div class="flex flex-wrap -m-4"> {% for lead in leads %} <div class="p-4 lg:w-1/2 md:w-full"> <div class="flex border-2 rounded-lg border-gray-200 border-opacity-50 p-8 sm:flex-row flex-col"> <div class="w-16 h-16 sm:mr-8 sm:mb-0 mb-4 inline-flex items-center justify-center rounded-full bg-indigo-100 text-indigo-500 flex-shrink-0"> <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-8 h-8" viewBox="0 0 24 24"> <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path> </svg> </div> <div class="flex-grow"> <h2 class="text-gray-900 text-lg title-font font-medium mb-3">{{ lead.first_name }} {{ lead.last_name }}</h2> <p class="leading-relaxed text-base">Blue bottle crucifix vinyl post-ironic four dollar toast vegan taxidermy. Gastropub indxgo juice poutine.</p> <a href="{% url 'leads:lead-detail' lead.pk %}" class="mt-3 text-indigo-500 inline-flex items-center"> View this lead <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> </div> </div> </div> {% endfor %} </div> {% if unassigned_leads.exists %} <div class="mt-5 flex flex-wrap -m-4"> <div class="p-4 w-full"> <h1 class="text-4xl text-gray-800">Unassigned leads</h1> </div> {% for lead in unassigned_leads %} <div class="p-4 w-full lg:w-1/2 md:w-full"> <div class="flex border-2 rounded-lg border-gray-200 border-opacity-50 p-8 sm:flex-row flex-col"> <div class="w-16 h-16 sm:mr-8 sm:mb-0 mb-4 inline-flex items-center justify-center rounded-full bg-indigo-100 text-indigo-500 flex-shrink-0"> <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-8 h-8" viewBox="0 0 24 24"> <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path> </svg> </div> <div class="flex-grow"> <h2 class="text-gray-900 text-lg title-font font-medium mb-3">{{ lead.first_name }} {{ lead.last_name }}</h2> <p class="leading-relaxed text-base">Blue bottle crucifix vinyl post-ironic four dollar toast vegan taxidermy. Gastropub indxgo juice poutine.</p> <a href="{% url 'leads:lead-detail' lead.pk %}" class="mt-3 text-indigo-500 inline-flex items-center"> View this lead <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> </div> </div> </div> {% endfor %} {% endif %} </div> </div> </section> {% endblock content %}
-
In templates/leads/views.py
from .forms import LeadForm, LeadModelForm, CustomUserCreationForm, AssignAgentForm : class AssignAgentView(OrganizerAndLoginRequiredMixin, genereic.FormView): template_name = "leads/assign_agent.html" form_class = AssignAgentForm def get_form_kwargs(self, **kwargs): kwargs = super(AssignAgentView, self).get_form_kwargs(**kwargs) kwargs.update({ "request": self.request }) return kwargs def get_success_url(self): return reverse("leads:lead-list") def form_valid(self, form): #print(form.cleaned_data["agent"]) agent = form.cleaned_data["agent"] lead = Lead.objects.get(id=self.kwargs["pk"]) lead.agent = agent lead.save() return super(AssignAgentView, self).form_valid(form)
-
Create the file temlates/leads/assign_agent.html
{% extends "base.html" %} {% block content %} <a href="{% url 'leads:lead-list' %}"> Go back to leads</a> <hr /> <h1> Assign an agent to this lead</h1> <form method="post"> <!-- form method="post" action="/leads/another-url/"> --> {% csrf_token %} {{ form.as_p }} <button type="Submit" >Submit</button> </form> {% endblock content %}
-
In leads/forms.py create the form to assign an agent
from .models import Lead Agent class AssignAgentForm(forms.Form): agent = forms.ModelChoiceField(queryset=Agent.objects.none()) def __init__(self, *args, **kwargs): request = kwargs.pop("request") agents = Agent.objects.filter(organization=request.user.userprofile) super(AssignAgentForm, self).__init__(*args, **kwargs) self.fields["agent"].queryset = agents
-
Edit the ./leads/urls.py
from .views import ( ... AssignAgentView) urlpatterns = [ path('<int:pk>/assign-agent/', AssignAgentView.as_view(), name='assign-agent'), ]
-
Update the leads/templates/lead_list.html
<div class="flex-grow"> <h2 class="text-gray-900 text-lg title-font font-medium mb-3">{{ lead.first_name }} {{ lead.last_name }}</h2> <p class="leading-relaxed text-base">Blue bottle crucifix vinyl post-ironic four dollar toast vegan taxidermy. Gastropub indxgo juice poutine.</p> <a href="{% url 'leads:assign-agent' lead.pk %}" class="mt-3 text-indigo-500 inline-flex items-center"> <!--this line--> Assign an agent <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> </div>
Test 44.1: Assign a lead to an agent
- In ./leads/models.py
class Lead(models.Model): first_name = models.CharField(max_length=20) last_name = models.CharField(max_length=20) age = models.IntegerFiled(default=0) organization = models.ForeignKey(UserProfile, on_delete=models.CASCADE) agent = models.ForeignKey("Agent", null=True, blank=True, on_delete=models.SET_NULL) category = models.ForeignKey("Category", null=True, blank=True, on_delete=models.SET_NULL) def __str__(self): return f"{self.first_name} {self.last_name}" class Category(models.Model): name = models.CharField(max_length=30) #New, Contacted, Converted, Unconverted def __str__(self): return self.name
- Modify the ./leads/admin.py
In the terminal:
from django.contrib import admin from .models import User, Lead, Agent. UserProfile, Category admin.site.register(Category) admin.site.register(User) admin.site.register(UserProfile) admin.site.register(Lead) admin.site.register(Agent)
Test 1: Create the categories {Contacted, Converted, Unconverted} from the admin site inpython manage.py makemigrations python manage.py migrate python manage.py runserver
http://127.0.0.1:8000/admin/leads/category/add
-
Edit ./leads/views.py
: class CategoryListView(LoginRequiredMixin, generic.ListView): template_name = "leads/category_list.html"
-
Edit ./leads/models.py and migrate with null the default properties
: class Category(models.Model): name = models.CharField(max_length=30) #New, Contacted, Converted, Unconverted organization = models.ForeignKey(UserProfile, null=True, blank=True, on_delete=models.CASCADE) #adding null and blank so that when the migration is made, the parameters don't require default values def __str__(self): return self.name
In the terminal run:
python manage.py makemigrations
andpython manage.py migrate
-
Modify again crm/leads/models.py and migrate without null and blank
: class Category(models.Model): name = models.CharField(max_length=30) #New, Contacted, Converted, Unconverted organization = models.ForeignKey(UserProfile, on_delete=models.CASCADE) #adding null and blank so that when the migration is made, the parameters don't require default values def __str__(self): return self.name
In the terminal run:
python manage.py makemigrations
and choose the option2) Ignore for now...
, then applypython manage.py migrate
and despite the error run the serverpython manage.py runserver
which yields an uapplied migration error. Go tohttp://127.0.0.1:8000/admin/leads/category/
and configure the organization of the categories, check Tes45.1 {Name: Contacted, Organization: jose, Name: Converted, Organization: jose, Name: Unconverted, Name: Jose}. Finally stop the server and migrate without problemspython manage.py migrate
and run the server -
In crm/leads/views.py
from .models import Lead, Agent, Category : class CategoryListView(LoginRequiredMixin, genereic.ListView): template_name = "leads/category_list.html" def get_queryset(self): user = self.request.user if user.is_organizer: queryset = Category.objects.filter(organization=user.userprofile) else: queryset = Category.objects.filter(organization=user.organization) return queryset
-
Create ./leads/templates/leads/category_list.html and paste the grabbed code from Tailblock (PRICING last option) at
https://mertjf.github.io/tailblocks/
inside the block content{% extends "base.html" %} {% block content %} <section class="text-gray-600 body-font"> <div class="container px-5 py-24 mx-auto"> <div class="flex flex-col text-center w-full mb-20"> <h1 class="sm:text-4xl text-3xl font-medium title-font mb-2 text-gray-900">Categories</h1> <p class="lg:w-2/3 mx-auto leading-relaxed text-base">These categories segment the leads</p> </div> <div class="lg:w-2/3 w-full mx-auto overflow-auto"> <table class="table-auto w-full text-left whitespace-no-wrap"> <thead> <tr> <th class="px-4 py-3 title-font tracking-wider font-medium text-gray-900 text-sm bg-gray-100 rounded-tl rounded-bl">Name</th> <th class="px-4 py-3 title-font tracking-wider font-medium text-gray-900 text-sm bg-gray-100">Lead Count</th> </tr> </thead> <tbody> <tr> <td class="px-4 py-3">Unassigned</td> <td class="px-4 py-3">{{ unassigned_lead_count }}</td> </tr> {% for category in category_list %} <tr> <td class="px-4 py-3">{{ category.name }}</td> <td class="px-4 py-3">TODO count</td> </tr> {% endfor %} </tbody> </table> </div> <div class="flex pl-4 mt-4 lg:w-2/3 w-full mx-auto"> <a class="text-indigo-500 inline-flex items-center md:mb-2 lg:mb-0">Learn More <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> <button class="flex ml-auto text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded">Button</button> </div> </div> </section> {% endblock content %}
-
Edit ./leads/views.py
class CategoryListView(LoginRequiredMixin, generic.ListView): template_name = "leads/category_list.html" context_object_name = "category_list" def get_context_data(self, **kwargs): context = super(CategoryListView, self).get_context_data(**kwargs) user = self.request.user if user.is_organizer: queryset = Lead.objects.filter(organization=user.userprofile) else: queryset = Lead.objects.filter(organization=user.agent.organization) context.update({ "unassigned_lead_count": queryset.filter(category__isnull=True).count()}) return context def get_queryset(self): user = self.request.user if user.is_organizer: queryset = Category.objects.filter(organization=user.userprofile) else: queryset = Category.objects.filter(organization=user.organization) return queryset
-
Edit ./leads/urls.py
from views.py import( LeadsListViews, ..., AssignAgentView, CategoryListView) urlpattern = [ : path('categories/', CategoryListView.as_view(), name='category-list'), ]
Test 46.1: Go to
http://127.0.0.1:8000/leads/categories
-
Edit ./leads/views.py
: class CategoryDetailView(LoginRequiredMixin, DetailView): template_name = "leads/category_detail.html" context_object_name = "category" def get_context_data(self, **kwargs): context = super(CategoryDetailView, self).get_context_data(**kwargs) leads = self.get_object().leads.all() context.update({ "leads": leads }) return context def get_queryset(self): user = self.request.user if user.is_organizer: queryset = Category.objects.filter(organization=user.userprofile) else: queryset = Category.objects.filter(organization=user.agent.organization) return queryset
-
Before the previous step, add a tag name (
related_name
) to the Category in the Lead model of ./leads/models.py: class Lead(models.Model): first_name = models.CharField(max_length=20) last_name = models.CharField(max_length=20) age = models.IntegerFiled(default=0) organization = models.ForeignKey(UserProfile, on_delete=models.CASCADE) agent = models.ForeignKey("Agent", null=True, blank=True, on_delete=models.SET_NULL) category = models.ForeignKey("Category", related_name = "leads", null=True, blank=True, on_delete=models.SET_NULL) :
-
Create ./leads/templates/leads/category_detail.html
{% extends "base.html" %} {% block content %} <section class="text-gray-600 body-font"> <div class="container px-5 py-24 mx-auto"> <div class="flex flex-col text-center w-full mb-20"> <h1 class="sm:text-4xl text-3xl font-medium title-font mb-2 text-gray-900">{{ category.name }}</h1> <p class="lg:w-2/3 mx-auto leading-relaxed text-base">These are the leads under this category</p> </div> <div class="lg:w-2/3 w-full mx-auto overflow-auto"> <table class="table-auto w-full text-left whitespace-no-wrap"> <thead> <tr> <th class="px-4 py-3 title-font tracking-wider font-medium text-gray-900 text-sm bg-gray-100 rounded-tl rounded-bl">First Name</th> <th class="px-4 py-3 title-font tracking-wider font-medium text-gray-900 text-sm bg-gray-100">Last Name</th> </tr> </thead> <tbody> {% for lead in leads %} <tr> <td class="px-4 py-3">{{ lead.first_name }}</td> <td class="px-4 py-3">{{ lead.last_name }}</td> </tr> {% endfor %} </tbody> </table> </div> <div class="flex pl-4 mt-4 lg:w-2/3 w-full mx-auto"> <a class="text-indigo-500 inline-flex items-center md:mb-2 lg:mb-0">Learn More <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> <button class="flex ml-auto text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded">Button</button> </div> </div> </section> {% endblock content %}
Run in the terminal:
python manage.py makemigrations python manage.py migrate python manage.py runserverv
-
Update the ./leads/urls.py to add the path of category-detail
from django.urls import path from .views import (LeadListView, ..., CategoryListView, CategoryDetailView) urlpatterns = [ : path('categories/<int:pk>/', CategoryDetailView.as_view(), name='category-detail'), ] path
-
Update the ./leads/templates/leads/category_list.html (See line 3438)
{% for category in category_list %} <tr> <td class="px-4 py-3"> <a href="{% url 'leads:category-detail' category.pk %}"> {{ category.name }}</a> </td> <td class="px-4 py-3">TODO count</td> </tr> {% endfor %}
-
Configure ./leads/templates/leads/lead_list.html (line 8)
{% extends "base.html" %} {% block content %} <section class="text-gray-600 body-font"> <div class="container px-5 py-24 mx-auto flex flex-wrap"> <div class="w-full mb-6 py-6 flex justify-between items-center border-b border-gray-200"> <div> <h1 class="text-4xl text-gray-800">Lead</h1> <a class="text-gray-500 hover:text-blue-500" href="{% url 'leads:category-list' %}"> Vuew categories </a> </div> {% if request.user.is_organizer %} <div> <a class="text-gray-500 hover:text-blue-500" href="{% url 'leads:lead-create' %}">Create a new lead</a> </div> {% endif %} </div>
Test: Go to
http://
127.0.0.1:8000/admin/leads/lead, choose a lead a configure his category (i.e. contacted)<br> Then go to
http://127.0.0.1:8000/leads/
, click onView categories
and view thecontacted
in the Name column and verify that the user is listed there -
Simplify the selection of leads in the category, in crm/leads/templates/leads/category_detail.html (line 28)
<tbody> {% for lead in category.leads.all %} <!-- {% for lead in leads %} --> <tr> <td class="px-4 py-3">{{ lead.first_name }}</td> <td class="px-4 py-3">{{ lead.last_name }}</td> </tr> {% endfor %} </tbody>
And comment the following section of crm/leads.views.py
: class CategoryDetailView(LoginRequiredMixin, DetailView): template_name = "leads/category_detail.html" context_object_name = "category" #def get_context_data(self, **kwargs): # context = super(CategoryDetailView, self).get_context_data(**kwargs) # leads self.get_object().leads.all() # # context.update({ # "leads": leads # }) # return context
- Update the category of a lead, in ./leads/category_datail.html (line 3468)
: <tbody> {% for lead in category.leads.all %} <tr> <td class="px-4 py-3"> <a class="hover:text-blue-500" href="{% url 'leads:lead-detail' lead.pk %}"> {{ lead.first_name }} </a> </td> <td class="px-4 py-3">{{ lead.last_name }}</td> </tr> {% endfor %} </tbody>
- Update leads/templates/leads/lead_detail.html line 15
{% extends "base.html" %} {% block content %} <section class="text-gray-600 body-font overflow-hidden"> <div class="container px-5 py-24 mx-auto"> <div class="lg:w-4/5 mx-auto flex flex-wrap"> <div class="lg:w-1/2 w-full lg:pr-10 lg:py-6 mb-6 lg:mb-0"> <h2 class="text-sm title-font text-gray-500 tracking-widest">Lead</h2> <h1 class="text-gray-900 text-3xl title-font font-medium mb-4">{{ lead.first_name }} {{ lead.last_name }}</h1> <div class="flex mb-4"> <a href="{% url 'leads:lead-detail' lead.pk %}" class="flex-grow text-indigo-500 border-b-2 border-indigo-500 py-2 text-lg px-1">Overview</a> <!-- interchaged --> <a class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Reviews</a> <!-- interchaged --> <a href="{% url 'leads:lead-update' lead.pk %}" class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Update Details</a> <!-- interchaged --> <a href="#" class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1"> Category </a> </div> :
- Add the class in ./leads/forms.py
class LeadCategoryUpdateForm(forms.ModelForm): class Meta: model = Lead fields = ( 'category', )
- Add the model leadcategoryupdateview in ./leads/views.py
from .forms import LeadForm, ..., AssignAgentForm, LeadCategoryUpdateForm class LeadCategoryUpdateView(LoginRequiredMixin, generic.UpdateView): template_name = "leads/lead_category_update.html" form_class = LeadCategoryUpdateForm def get_queryset(self): user = self.request.user if user.is_organizer: queryset = Lead.objects.filter(organization=user.userprofile) else: queryset = Lead.objects.filter(organization=user.agent.organization) queryset = queryset.filter(agent__user=user) return queryset def get_success_url(self): return reverse("leads:lead-detail", kwargs={"pk": self.get_object().id})
- Update in ./leads/urls.py
from .views import (LeadListView, ...,CategoryDetailView, LeadCategoryUpdateView) ; urlpatterns = [ : path('<int:pk>/category/', LeadCategoryUpdateView.as_view(), name='lead-category-update'), ]
- Update leads/templates/leads/lead_detail.html, line 15
{% extends "base.html" %} {% block content %} <section class="text-gray-600 body-font overflow-hidden"> <div class="container px-5 py-24 mx-auto"> <div class="lg:w-4/5 mx-auto flex flex-wrap"> <div class="lg:w-1/2 w-full lg:pr-10 lg:py-6 mb-6 lg:mb-0"> <h2 class="text-sm title-font text-gray-500 tracking-widest">Lead</h2> <h1 class="text-gray-900 text-3xl title-font font-medium mb-4">{{ lead.first_name }} {{ lead.last_name }}</h1> <div class="flex mb-4"> <a href="{% url 'leads:lead-detail' lead.pk %}" class="flex-grow text-indigo-500 border-b-2 border-indigo-500 py-2 text-lg px-1">Overview</a> <!-- interchaged --> <!--<a class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Reviews</a> interchaged --> <a href="{% url 'leads:lead-category-update' lead.pk %}" class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1"> Category </a> <a href="{% url 'leads:lead-update' lead.pk %}" class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Update Details</a> <!-- interchaged --> </div> </a> :
- Create the template: leads/templates/leads/lead_category_update.html
And edit the crm/leads/templates/leads/lead_update.html, line 15
{% extends "base.html" %} {% block content %} <section class="text-gray-600 body-font overflow-hidden"> <div class="container px-5 py-24 mx-auto"> <div class="lg:w-4/5 mx-auto flex flex-wrap"> <div class="lg:w-1/2 w-full lg:pr-10 lg:py-6 mb-6 lg:mb-0"> <h2 class="text-sm title-font text-gray-500 tracking-widest">LEAD</h2> <h1 class="text-gray-900 text-3xl title-font font-medium mb-4">{{ lead.first_name }} {{ lead.last_name }}</h1> <div class="flex mb-4"> <a href="{% url 'leads:lead-detail' lead.pk %}" class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Overview</a> <a href="{% url 'leads:lead-category-update' lead.pk %}" class="flex-grow text-indigo-500 border-b-2 border-indigo-500 py-2 text-lg px-1">Category</a> <a href="{% url 'leads:lead-update' lead.pk %}" class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Update Details</a> </div> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Submit</button> </form> <a href="{% url 'leads:lead-delete' lead.pk %}" class="w-1/2 mt-3 flex ml-auto text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded">Delete</a> </div> <img alt="ecommerce" class="lg:w-1/2 w-full lg:h-auto h-64 object-cover object-center rounded" src="https://dummyimage.com/400x400"> </div> </div> </section> {% endblock content %}
Test 48.1 by going to:{% extends "base.html" %} {% block content %} <section class="text-gray-600 body-font overflow-hidden"> <div class="container px-5 py-24 mx-auto"> <div class="lg:w-4/5 mx-auto flex flex-wrap"> <div class="lg:w-1/2 w-full lg:pr-10 lg:py-6 mb-6 lg:mb-0"> <h2 class="text-sm title-font text-gray-500 tracking-widest">LEAD</h2> <h1 class="text-gray-900 text-3xl title-font font-medium mb-4">{{ lead.first_name }} {{ lead.last_name }}</h1> <div class="flex mb-4"> <a href="{% url 'leads:lead-detail' lead.pk %}" class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Overview</a> <a href="{% url 'leads:lead-category-update' lead.pk %}" class = "flex-grow border-b-2 border-gray-300 py-2 text-lg px-2">Category</a> <a href="{% url 'leads:lead-update' lead.pk %}" class="flex-grow text-indigo-500 border-b-2 border-indigo-500 py-2 text-lg px-1">Update Details</a> </div> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Submit</button> </form> <a href="{% url 'leads:lead-delete' lead.pk %}" class="w-1/2 mt-3 flex ml-auto text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded">Delete</a> </div> <img alt="ecommerce" class="lg:w-1/2 w-full lg:h-auto h-64 object-cover object-center rounded" src="https://dummyimage.com/400x400"> </div> </div> </section> {% endblock content %}
http://127.0.0.1:8000/leads/1/category
and changing the category of the lead, checkver-1.6
-
In the terminal
pip install django-crispy-forms pip install crispy-tailwind pip freeze > requirements.txt
-
Add the app in crm/settings.py
INSTALLED_APPS = [ : 'agents', 'crispy_forms', 'crispy_tailwind', ] : #in the bottom : CRISPY_ALLOWED_TEMPLATE_PACKS = "tailwind" CRISPY_TEMPLATE_PACK = "tailwind"
-
In templates/registration/login.html, add
{% load tailwind_filters %}
after{% extends 'base.html' %}
and replace {{ form.as_p }} with {{ form|crispy }}
Also make the same changes in password_reset_{complete,confirm,done,email,form}.html, signup.html files
For login.html:{% extends 'base.html' %} {% load tailwind_filters %} {% block content %} <div class="max-w-lg mx-auto"> <div class="py-5 border-t border-gray-200"> <a class = "hover:text-blue-500" href="{% url 'signup' %}">Don't have an account?</a> </div> <form method = "post" class="mt-5"> {% csrf_token %} {{ form|crispy }} <button type='submit' class="w-full text-white bg-blue-500 hover:bg-blue-600 px-3 py-2 rounded-md">Login</button> </form> <div class="py-5 border-t border-gray-200 mt-5"> <a class = "hover:text-blue-500" href="{% url 'reset-password' %}">Forgot password?</a> </div> </div> {% endblock content %}
Test: Go to
http://127.0.0.1:8000/login/
and see the changesIn password_reset_confirm.html
{% extends 'base.html' %} {% load tailwind_filters %} {% block content %} <div class = "max-w-lg mx-auto"> <h1 class="text-4xl text-gray-800">Enter your new password</h1> <form method="post"> {% csrf_token %} {{ form|crispy }} <button type='submit' class="w-full text-white bg-blue-500 hover:bg-blue-600 px-3 py-2 rounded-md">Confirm new password</button> </form> </div> {% endblock content %}
In password_reset_form.html
{% extends 'base.html' %} {% load tailwind_filters %} {% block content %} <div class = "max-w-lg mx-auto"> <h1 class="text-4xl text-gray-800">Enter your new password</h1> <form method="post" class="mt-5"> {% csrf_token %} {{ form|crispy }} <button type='submit' class="w-full text-white bg-blue-500 hover:bg-blue-600 px-3 py-2 rounded-md">Reset password</button> </form> <div class = "py-5 border-t border-gray-200 mt-5"> <a class="hover:text-blue-500" href="{% url 'login' %}">Already have an account?</a> </div> </div> {% endblock content %}
In the ./templates/registration/signup.html
{% extends 'base.html' %} {% load tailwind_filters %} {% block content %} <div class="max-w-lg mx-auto"> <form method = "post" class="mt-5"> {% csrf_token %} {{ form|crispy }} <button tpe='submit' class="w-full text-white bg-blue-500 hover:bg-blue-600 px-3 py-2 rounded-md">Signup</button> </form> <div class="py-5 border-t border-gray-200 mt-5"> <a class = "hover:text-blue-500" href="{% url 'login' %}">Already have an account?</a> </div> </div> {% endblock content %}
Also edit in leads/templates/leads/lead_create.html
{% extends "base.html" %} {% load tailwind_filters %} {% block content %} <div class="max-w-lg mx-auto"> <a class="hover:text-blue-500" href="{% url 'leads:lead-list' %}">Go back to leads</a> <div class="py-5 border-t border-gray-200"> <h1>Create a new lead</h1> </div> <form method="post" class="mt-5"> {% csrf_token %} {{ form|crispy }} <button type='submit' class="w-full text-white bg-blue-500 hover:bg-blue-600 px-3 py-2 rounded-md">Submit</button> </form> </div> {% endblock content %}
In crm/templates/leads/lead_update.html
{% extends "base.html" %} {% load tailwind_filters %} {% block content %} <section class="text-gray-600 body-font overflow-hidden"> <div class="container px-5 py-24 mx-auto"> <div class="lg:w-4/5 mx-auto flex flex-wrap"> <div class="lg:w-1/2 w-full lg:pr-10 lg:py-6 mb-6 lg:mb-0"> <h2 class="text-sm title-font text-gray-500 tracking-widest">Lead</h2> <h1 class="text-gray-900 text-3xl title-font font-medium mb-4">{{ lead.first_name }} {{ lead.last_name }}</h1> <div class="flex mb-4"> <a href="{% url 'leads:lead-detail' lead.pk %}" class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Overview</a> <!-- interchaged --> <!--<a class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Reviews</a> interchaged --> <a href="{% url 'leads:lead-category-update' lead.pk %}" class = "flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Category</a> <a href="{% url 'leads:lead-update' lead.pk %}" class="flex-grow text-indigo-500 border-b-2 border-indigo-500 py-2 text-lg px-1">Update Details</a> <!-- interchaged --> </div> <form method="post"> {% csrf_token %} {{ form|crispy }} <button class="w-full text-white bg-blue-500 hover:bg-blue-600 px-3 py-2 rounded-md type="submit">Submit</button> </form> <div class="mt-5 py-5 border-t border-gray-200"> <a href="{% url 'leads:lead-delete' lead.pk %}" class="w-1/2 mt-3 text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded">Delete</a> </div> </div> </div> </div> </section> {% endblock content %}`
-
Update the leads/templates/leads/lead_category_update.html
{% extends "base.html" %} {% load tailwind_filters %} {% block content %} <section class="text-gray-600 body-font overflow-hidden"> <div class="container px-5 py-24 mx-auto"> <div class="lg:w-4/5 mx-auto flex flex-wrap"> <div class="lg:w-1/2 w-full lg:pr-10 lg:py-6 mb-6 lg:mb-0"> <h2 class="text-sm title-font text-gray-500 tracking-widest">LEAD</h2> <h1 class="text-gray-900 text-3xl title-font font-medium mb-4">{{ lead.first_name }} {{ lead.last_name }}</h1> <div class="flex mb-4"> <a href="{% url 'leads:lead-detail' lead.pk %}" class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Overview</a> <a href="{% url 'leads:lead-category-update' lead.pk %}" class="flex-grow text-indigo-500 border-b-2 border-indigo-500 py-2 text-lg px-1">Category</a> <a href="{% url 'leads:lead-update' lead.pk %}" class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Update Details</a> </div> <form method="post"> {% csrf_token %} {{ form|crispy }} <button type="submit">Submit</button> </form> <a href="{% url 'leads:lead-delete' lead.pk %}" class="w-1/2 mt-3 flex ml-auto text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded">Delete</a> </div> <img alt="ecommerce" class="lg:w-1/2 w-full lg:h-auto h-64 object-cover object-center rounded" src="https://dummyimage.com/400x400"> </div> </div> </section> {% endblock content %}
-
Update agent/templates/agents/agent_create.html
{% extends "base.html" %} {% load tailwind_filters %} {% block content %} <div class="max-w-lg mx-auto"> <div class="py-5 border-b border-gray-200"> <a class="hover:text-blue-500" href="{% url 'agents:agent-list' %}">Go back to agents</a> </div> <h1 class="text-4xl text-gray-800"> Create a new agent</h1> <form method="post"> {% csrf_token %} {{ form|crispy }} <button type="submit" class="w-full text-white bg-blue-500 hover:bg:blue-600 px-3 py-2 rounded-md">Submit</button> </form> </div> {% endblock content %}
Update agents/templates/agents/agent_update.html
{% extends "base.html" %} {% load tailwind_filters %} {% block content %} <section class="text-gray-600 body-font overflow-hidden"> <div class="container px-5 py-24 mx-auto"> <div class="lg:w-4/5 mx-auto flex flex-wrap"> <div class="lg:w-1/2 w-full lg:pr-10 lg:py-6 mb-6 lg:mb-0"> <h2 class="text-sm title-font text-gray-500 tracking-widest">AGENT</h2> <h1 class="text-gray-900 text-3xl title-font font-medium mb-4">{{ agent.user.username }}</h1> <div class="flex mb-4"> <a href="{% url 'agents:agent-detail' agent.pk %}" class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Overview</a> <!-- interchaged --> <a class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Reviews</a> <!-- interchaged --> <a href="{% url 'agents:agent-update' agent.pk %}" class="flex-grow text-indigo-500 border-b-2 border-indigo-500 py-2 text-lg px-1">Update Details</a> <!-- interchaged --> </div> <form method="post"> {% csrf_token %} {{ form|crispy }} <button type="submit" class="w-full text-white bg-blue-500 hover:bg:blue-600 px-3 py-2 rounded-md">Submit</button> </form> <div class="mt-5 py-5 border-t border-gray-200"> <a href="{% url 'agents:agent-delete' agent.pk %}" class="w-1/2 mt-3 text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded">Delete</a> </div> <img alt="ecommerce" class="lg:w-1/2 w-full lg:h-auto h-64 object-cover object-center rounded" src="https://dummyimage.com/400x400"> </div> </div> </section> {% endblock content %}
-
Update the agent_delete.html
{% extends "base.html" %} {% load tailwind_filters %} {% block content %} <div class="max-w-lg mx-auto"> <div class="py-5 border-b border-gray-200"> <a class="hover:text-blue-500" href="{% url 'agents:agent-list' %}">Go back to agents</a> </div> <h1 class="text-3xl text-gray-800">Are you sure you want to delete this agent?</h1> <form method="post" class="mt-5"> {% csrf_token %} {{ form|crispy }} <button type="submit" class="w-full text-white bg-blue-500 hover:bg:blue-600 px-3 py-2 rounded-md">Submit</button> </form> </div> {% endblock content %} ``
-
Update the lead_delete.html
{% extends "base.html" %} {% load tailwind_filters %} {% block content %} <div class="max-w-lg mx-auto"> <div class="py-5 border-b border-gray-200"> <a class="hover:text-blue-500" href="{% url 'leads:lead-list' %}">Go back to leads</a> </div> <h1 class="text-3xl text-gray-800">Are you sure you want to delete this lead?</h1> <form method="post" class="mt-5"> {% csrf_token %} {{ form|crispy }} <button type="submit" class="w-full text-white bg-blue-500 hover:bg:blue-600 px-3 py-2 rounded-md">Submit</button> </form> </div> {% endblock content %}
-
Update leads/templates/leads/assign_agent.html
{% extends "base.html" %} {% load tailwind_filters %} {% block content %} <div class="max-w-lg mx-auto"> <div class="py-5 border-b border-gray-200"> <a class="hover:text-blue-500" href="{% url 'leads:lead-list' %}">Go back to leads</a> </div> <hr /> <h1 class="text-3xl text-gray-800">Assign an agent to this lead</h1> <form method="post" class="mt-5"> {% csrf_token %} {{ form|crispy }} <button type="submit" class="w-full text-white bg-blue-500 hover:bg:blue-600 px-3 py-2 rounded-md">Submit</button> </form> </div> {% endblock content %}
- Go to leads/models.py and add {descrip, date, phone, email} to the model
class Lead(models.Model): first_name = models.CharField(max_length=20) last_name = models.CharField(max_length=20) age = models.IntegerFiled(default=0) organization = models.ForeignKey(UserProfile, on_delete=models.CASCADE) agent = models.ForeignKey("Agent", null=True, blank=True, on_delete=models.SET_NULL) category = models.ForeignKey("Category", related_name = "leads", null=True, blank=True, on_delete=models.SET_NULL) description = models.TextField() date_added = models.DateTimmeField(auto_now_add=True) phone_number = models.CharField(max_length=20) email = models.EmailField() :
- In the terminal:
Do for all the leads that were created and end with
python manage.py makemigrations Selection an otion: 1 >> timezone.now
python manage.py migrate python manage.py runserver
- Edit the leads/forms.py
Then run again
class LeadModelForm(forms.ModelForm): class Meta: model = Lead fields = ( 'first_name', 'last_name', 'age', 'agent', 'description', 'phone_number', 'email', )
python manage.py runserver
- Fix in ./leads/view.py/ 50 - 4:40 Error at creating a lead in
http://127.0.0.1:8000/leads/create
, after clicking on submitclass LeadCreateView(OrganizerAndLoginRequiredMixin, generic.CreateView): template_name = "leads/lead_create.html" form_class = LeadModelForm def get_success_url(self): return reverse("leads:lead-list") def form_valid(self, form): lead = form.save(commit=False) lead.organization = self.request.user.userprofile lead.save() send_mail( subject="A lead has been created", message="Go to the website to see the new lead", from_email="test@test.com", recipient_list=["test2@test.com"] ) return super(LeadCreateView, self).form_valid(form)
- Edit the leads/templates/leads/lead_list.html file
{% extends "base.html" %} {% block content %} <section class="text-gray-700 body-font"> <div class="container px-5 py-24 mx-auto flex flex-wrap"> <div class="w-full mb-6 py-6 flex justify-between items-center border-b border-gray-200"> <div> <h1 class="text-4xl text-gray-800">Leads</h1> <a class="text-gray-500 hover:text-blue-500" href="{% url 'leads:category-list' %}"> View categories </a> </div> {% if request.user.is_organizer %} <div> <a class="text-gray-500 hover:text-blue-500" href="{% url 'leads:lead-create' %}"> Create a new lead </a> </div> {% endif %} </div> <div class="flex flex-col w-full"> <div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8"> <div class="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"> <div class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"> <table class="min-w-full divide-y divide-gray-200"> <thead class="bg-gray-50"> <tr> <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> First Name </th> <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> Last Name </th> <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> Age </th> <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> Email </th> <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> Cell Phone Number </th> <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> Category </th> <!-- <th scope="col" class="relative px-6 py-3"> <span class="sr-only">Edit</span> </th> --> <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> Modify </th> </tr> </thead> <tbody> {% for lead in leads %} <tr class="bg-white"> <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900"> <a class="text-blue-500 hover:text-blue-800" href="{% url 'leads:lead-detail' lead.pk %}">{{ lead.first_name }}</a> </td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> {{ lead.last_name }} </td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> {{ lead.age }} </td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> {{ lead.email }} </td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> {{ lead.phone_number }} </td> <td class="px-6 py-4 whitespace-nowrap"> {% if lead.category %} <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"> {{ lead.category.name }} </span> {% else %} <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"> Unassigned </span> {% endif %} </td> <td class="px-8 py-4 whitespace-nowrap text-sm font-medium"> <a href="{% url 'leads:lead-update' lead.pk %}" class="text-indigo-600 hover:text-indigo-900"> Edit </a> </td> </tr> {% empty %} <p>There are currently no leads</p> {% endfor %} </tbody> </table> </div> </div> </div> </div> {% if unassigned_leads.exists %} <div class="mt-5 flex flex-wrap -m-4"> <div class="p-4 w-full"> <h1 class="text-4xl text-gray-800">Unassigned leads</h1> </div> {% for lead in unassigned_leads %} <div class="p-4 lg:w-1/2 md:w-full"> <div class="flex border-2 rounded-lg border-gray-200 p-8 sm:flex-row flex-col"> <div class="w-16 h-16 sm:mr-8 sm:mb-0 mb-4 inline-flex items-center justify-center rounded-full bg-indigo-100 text-indigo-500 flex-shrink-0"> <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-8 h-8" viewBox="0 0 24 24"> <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path> </svg> </div> <div class="flex-grow"> <h2 class="text-gray-900 text-lg title-font font-medium mb-3"> {{ lead.first_name }} {{ lead.last_name }} </h2> <p class="leading-relaxed text-base"> {{ lead.description }} </p> <a href="{% url 'leads:assign-agent' lead.pk %}" class="mt-3 text-indigo-500 inline-flex items-center"> Assign an agent <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> </div> </div> </div> {% endfor %} </div> {% endif %} </div> </section> {% endblock content %} <!-- extends "base.html" block content <section class="text-gray-600 body-font"> <div class="container px-5 py-24 mx-auto flex flex-wrap"> <div class="w-full mb-6 py-6 flex justify-between items-center border-b border-gray-200"> <div> <h1 class="text-4xl text-gray-800">Lead</h1> <a class="text-gray-500 hover:text-blue-500" href="{% url 'leads:category-list' %}"> Vuew categories </a> </div> {% if request.user.is_organizer %} <div> <a class="text-gray-500 hover:text-blue-500" href="{% url 'leads:lead-create' %}">Create a new lead</a> </div> {% endif %} </div> <div class="flex flex-wrap -m-4"> {% for lead in leads %} <div class="p-4 lg:w-1/2 md:w-full"> <div class="flex border-2 rounded-lg border-gray-200 border-opacity-50 p-8 sm:flex-row flex-col"> <div class="w-16 h-16 sm:mr-8 sm:mb-0 mb-4 inline-flex items-center justify-center rounded-full bg-indigo-100 text-indigo-500 flex-shrink-0"> <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-8 h-8" viewBox="0 0 24 24"> <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path> </svg> </div> <div class="flex-grow"> <h2 class="text-gray-900 text-lg title-font font-medium mb-3">{{ lead.first_name }} {{ lead.last_name }}</h2> <p class="leading-relaxed text-base">Blue bottle crucifix vinyl post-ironic four dollar toast vegan taxidermy. Gastropub indxgo juice poutine.</p> <a href="{% url 'leads:lead-detail' lead.pk %}" class="mt-3 text-indigo-500 inline-flex items-center"> View this lead <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> </div> </div> </div> {% endfor %} </div> {% if unassigned_leads.exists %} <div class="mt-5 flex flex-wrap -m-4"> <div class="p-4 w-full"> <h1 class="text-4xl text-gray-800">Unassigned leads</h1> </div> {% for lead in unassigned_leads %} <div class="p-4 w-full lg:w-1/2 md:w-full"> <div class="flex border-2 rounded-lg border-gray-200 border-opacity-50 p-8 sm:flex-row flex-col"> <div class="w-16 h-16 sm:mr-8 sm:mb-0 mb-4 inline-flex items-center justify-center rounded-full bg-indigo-100 text-indigo-500 flex-shrink-0"> <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-8 h-8" viewBox="0 0 24 24"> <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path> </svg> </div> <div class="flex-grow"> <h2 class="text-gray-900 text-lg title-font font-medium mb-3">{{ lead.first_name }} {{ lead.last_name }}</h2> <p class="leading-relaxed text-base"> {{ lead.description }} </p> <a href="{% url 'leads:assign-agent' lead.pk %}" class="mt-3 text-indigo-500 inline-flex items-center"> Assign an agent <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> </div> </div> </div> {% endfor %} </div> {% endif %} </div> </section> endblock content -->
- Change the default redirect after loggin out, go to
crm/settings.py
: AUTH_USER_MODEL = 'leads.User' EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" LOGIN_REDIRECT_URL = "/leads" LOGIN_URL = "/login" LOGOUT_REDIRECT_URL = "/" CRISPY_ALLOWED_TEMPLATE_PACKS = "tailwind" CRISPY_TEMPLATE_PACK = 'tailwind'
- Update the leads/templates/leads/lead_detail.html
{% extends "base.html" %} {% block content %} <section class="text-gray-600 body-font overflow-hidden"> <div class="container px-5 py-24 mx-auto"> <div class="lg:w-4/5 mx-auto flex flex-wrap"> <div class="lg:w-1/2 w-full lg:pr-10 lg:py-6 mb-6 lg:mb-0"> <h2 class="text-sm title-font text-gray-500 tracking-widest">Lead</h2> <h1 class="text-gray-900 text-3xl title-font font-medium mb-4">{{ lead.first_name }} {{ lead.last_name }}</h1> <div class="flex mb-4"> <a href="{% url 'leads:lead-detail' lead.pk %}" class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Overview</a> <a href="{% url 'leads:lead-category-update' lead.pk %}" class="flex-grow text-indigo-500 border-b-2 border-indigo-500 py-2 text-lg px-1">Category</a> <a href="{% url 'leads:lead-update' lead.pk %}" class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Update Details</a> </div> <p class="leading-relaxed mb-4">{{ lead.description }}</p> <div class="flex border-t border-gray-200 py-2"> <span class="text-gray-500">Age</span> <span class="ml-auto text-gray-900">{{ lead.age }}</span> </div> <div class="flex border-t border-gray-200 py-2"> <span class="text-gray-500">Email</span> <span class="ml-auto text-gray-900">{{ lead.email }}</span> </div> <div class="flex border-t border-b mb-6 border-gray-200 py-2"> <span class="text-gray-500">Phone Number</span> <span class="ml-auto text-gray-900">{{ lead.phone_number }}</span> </div> <div class="flex"> <!-- <span class="title-font font-medium text-2xl text-gray-900">$58.00</span> --> <button class="flex ml-auto text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded">Button</button> <button class="rounded-full w-10 h-10 bg-gray-200 p-0 border-0 inline-flex items-center justify-center text-gray-500 ml-4"> <svg fill="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-5 h-5" viewBox="0 0 24 24"> <path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"></path> </svg> </button> </div> </div> <img alt="ecommerce" class="lg:w-1/2 w-full lg:h-auto h-64 object-cover object-center rounded" src="https://dummyimage.com/400x400"> </div> </div> </section> {% endblock content %}
- Update ./leads/templates/leads/lead_update.html
Test 50-1 Compiled in the branch of
{% extends "base.html" %} {% load tailwind_filters %} {% block content %} <section class="text-gray-600 body-font overflow-hidden"> <div class="container px-5 py-24 mx-auto"> <div class="lg:w-4/5 mx-auto flex flex-wrap"> <div class="w-full lg:pr-10 lg:py-6 mb-6 lg:mb-0"> <!-- <div class="lg:w-1/2 w-full lg:pr-10 lg:py-6 mb-6 lg:mb-0"> --> <h2 class="text-sm title-font text-gray-500 tracking-widest">Lead</h2> <h1 class="text-gray-900 text-3xl title-font font-medium mb-4">{{ lead.first_name }} {{ lead.last_name }}</h1> <div class="flex mb-4"> <a href="{% url 'leads:lead-detail' lead.pk %}" class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Overview</a> <!-- interchaged --> <!--<a class="flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Reviews</a> interchaged --> <a href="{% url 'leads:lead-category-update' lead.pk %}" class = "flex-grow border-b-2 border-gray-300 py-2 text-lg px-1">Category</a> <a href="{% url 'leads:lead-update' lead.pk %}" class="flex-grow text-indigo-500 border-b-2 border-indigo-500 py-2 text-lg px-1">Update Details</a> <!-- interchaged --> </div> <form method="post"> {% csrf_token %} {{ form|crispy }} <button class="w-full text-white bg-blue-500 hover:bg-blue-600 px-3 py-2 rounded-md type="submit">Submit</button> </form> <div class="mt-5 py-5 border-t border-gray-200"> <a href="{% url 'leads:lead-delete' lead.pk %}" class="w-1/2 mt-3 text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded">Delete</a> </div> </div> </div> </div> </section> {% endblock content %}
ver-1.7
-
In the terminal
pip install django-environ pip freeze > requirements.txt
-
Edit the crm/settings.py, add the environ package
from pathlib import Path import environ env = environ.Env( DEBUG = (bool, False) ) environ.Env.read_env() :
-
Create the new file crm/.env and copy the the secret_key of crm/settings.py
DEBUG=True SECRET_KEY='9l=jjp#pg0-mbdfsntqww91&s9b^^a!kj44ljl4f5h!_uoft$h6u'
-
Create another .env that will be shown uploaded with the projects and where the secret variables don't show up: .template.env
DEBUG=True SECRET_KEY=
-
Edit crm/settings.py to test the mechanism of variables, line 8
environ.ENV.read_env() DEBUG = env('DEBUG') SECRET_KEY = env('SECRET_KEY') print(DEBUG, SECRET_KEY)
Test: Stop and Run the server
python manage.py runserver
, verify on the terminal the printed key
-
Download and install it from
https://postgresql.org/download/
-
Change PosgreSQL password, but before Reset Master Password in the program pgAdmin4.
- Edit the file Files/PostgreSQL/14/data/pg_hba.conf, replace scram-sha-256 with trust # TYPE DATABASE USER ADDRESS METHOD # "local" is for Unix domain socket connections only local all all trust # IPv4 local connections: host all all 127.0.0.1/32 trust # IPv6 local connections: host all all ::1/128 trust # Allow replication connections from localhost, by a user with the # replication privilege. local replication all trust host replication all 127.0.0.1/32 trust host replication all ::1/128 trust - Go to CMD and type, wont ask you a password psql -U postgres - In postgres=#, change the password \password postgres - Restore Program Files/PostgreSQL/14/data/pg_hba.conf # TYPE DATABASE USER ADDRESS METHOD # "local" is for Unix domain socket connections only local all all scram-sha-256 # IPv4 local connections: host all all 127.0.0.1/32 scram-sha-256 # IPv6 local connections: host all all ::1/128 scram-sha-256 # Allow replication connections from localhost, by a user with the # replication privilege. local replication all scram-sha-256 host replication all 127.0.0.1/32 scram-sha-256 host replication all ::1/128 scram-sha-256 - Try to login again in CMD psql -U postgres
-
Create the data base
dbcrm
, in the VS code terminal and being in the environment, run:
Previously install postgreSQL extension for VScreatedb -h localhost -p 5432 -U postgres dbcrm #(Input password of pgAdmin4 or the the admin's pass of the program in the installation process)
-
Configure the database in crm/settings.py
: DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': env("DB_NAME"), 'USER': env("DB_USER"), 'PASSWORD': env("DB_PASSWORD"), 'HOST': env("DB_HOST"), 'PORT': env("DB_PORT"), } }
-
Configure the environment variables in crm/.env
DEBUG=True SECRET_KEY='9l=jjp#pg0-mbdfsntqww91&s9b^^a!kj44ljl4f5h!_uoft$h6u' DB_NAME = dbcrm DB_USER = dbcrmuser DB_PASSWORD = dbcrm1234 DB_HOST = localhost DB_PORT =
-
Access to the database from the terminal and create the user
dbcrmuser
that will take control over the databasedbcrm
psql -U postgres #password of the pgadmin4 (postgres) CREATE USER dbcrmuser WITH PASSWORD 'dbcrm1234'; GRANT ALL PRIVILEGES ON DATABASE dbcrm TO dbcrmuser \q
-
Copy the data to crm/.templates.env
DEBUG=True SECRET_KEY=1234 DB_NAME = DB_USER = DB_PASSWORD = DB_HOST = DB_PORT =
-
In the terminal, migrate the new database that's using PostgreSQL, be sure to delete all previous migrations files of the lead and agent apps, except
__init__.py
pip install psycopg2-binary python manage.py migrate python manage.py runserver
- In the terminal
pip install whitenoise pip freeze > requirements.txt
- Add the whitenoise in crm/settings.py
Run in the terminal:
: INSTALLED_APPS = [ 'whitenoise.runserver_nostatic', ] : MIDDLEWARE = [ 'whitenoise.middleware.WhiteNoiseMiddleware', : ] STATIC_ROOT = "static_root" STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' :
python manage.py collectstatic
- Edit the crm/settings.py
: if not DEBUG: SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True SECURE_CSRF_COOKIE_SECURE = True SECURE_BROWSER_XSS_FILTER = True SECURE_CONTENT_TYPE_NOSNIFF = True SECURE_HSTS_SECONDS = 31536000 #1YEAR SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_HSTS_PRELOAD = True X_FRAME_OPTIONS = "DENY" ALLOWED_HOSTS = ["*"] #YOUR DOMAIN.CO
- Install gunicorn:
pip install gunicorn
pip install gunicorn pip freeze > requirements.txt
Ommit this part for production
- Collect the static files, create ./runserver.sh(give executable permissions to this file)
In the terminal give the permission
python manage.py collectstatic --no-input python manage.py migrate gunicorn --worker-tmp-dir /dev/shm crm.sdgi
chmod +x runserver.h
. This won't run in the local server because the line of gunicorn --worker ... is meant to be run in a production server.
- Sign up in mailgun, then go to Sendinds>Domains>New Domain
SMTP credentials > Reset password: If there is no user, Create is on the tab Add new SMTP user: jose (jose@mg.yoursite.com) - Edit the crm/settings.py
: EMAIL_HOST = env("EMAIL_HOST") EMAIL_HOST_USER = env("EMAIL_HOST_USER") EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD") EMAIL_USE_TLS = True EMAIL_PORT = env("EMAIL_PORT") DEFAULT_FROM_EMAIL = env("DEFAULT_FROM_EMAIL")
- Add it to the crm/.env
In digital ocean add the environmet variables
: EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" EMAIL_HOST = "smtp.mailgun.org" EMAIL_HOST_USER = "postmaster@mg.domain.com" EMAIL_HOST_PASSWORD = "" EMAIL_USE_TLS = True EMAIL_PORT = 587 DEFAULT_FROM_EMAIL =
- Go to digital ocean and add enter your custom domain
Ommitted till here
- Go to leads/templates/leads/lead_list.html & agents/templates/agents/agent_list.html add before the {% endfor %}
: {% empty %} <p> There are currently no leads (agents) </p> {% endfor %}
- Edit the leads/templates/leads/assign_agent.html
{% extends "base.html" %} {% load tailwind_filters %} {% block content %} <div class="max-w-lg mx-auto"> <a class="hover:text-blue-500" href="{% url 'leads:lead-list' %}">Go back to leads</a> <div class="py-5 border-t border-gray-200"> <h1>Assign an agent to this lead</h1 </div> <form method="post" class="mt-5"> {% csrf_token %} {{ form|crispy }} <button type='submit' class="w-full text-white bg-blue-500 hover:bg-blue-600 px-3 py-2 rounded-md">Submit</button> </form> </div> {% endblock content %}
- Creat the category form in ./leads/forms.py
from .models import Lead, Agent, Category : class CategoryModelForm(forms.ModelForm): class Meta: model = Category fields = ( 'name', )
- Create a category view in ./leads/views.py
from .forms import ( LeadForm, LeadModelForm, CustomUserCreationForm, AssignAgentForm, LeadCategoryUpdateForm, CategoryModelForm ) : class CategoryCreateView(OrganizerAndLoginRequiredMixin, generic.CreateView): template_name = "leads/category_create.html" form_class = CategoryModelForm def get_success_url(self): return reverse("leads:category-list") def form_valid(self, form): category=form.save(commit=False) category.organization = self.request.user.userprofile category.save() return super(CategoryCreateView, self).form_valid(form) class CategoryUpdateView(OrganizerAndLoginRequiredMixin, generic.UpdateView): template_name = "leads/category_update.html" form_class = CategoryModelForm def get_success_url(self): return reverse("leads:category-list") def get_queryset(self): user = self.request.user if user.is_organizer: queryset = Category.objects.filter(organization=user.userprofile) else: queryset = Category.objects.filter(organization=user.agent.organization) return queryset class CategoryDeleteView(OrganizerAndLoginRequiredMixin, generic.DeleteView): template_name = "leads/category_delete.html" def get_success_url(self): return reverse("leads:category-list") def get_queryset(self): user = self.request.user if user.is_organizer: queryset = Category.objects.filter(organization=user.userprofile) else: queryset = Category.objects.filter(organization=user.agent.organization) return queryset
- Create a category_create.html file in leads/templates/leads
{% extends "base.html" %} {% load tailwind_filters %} {% block content %} <div class="max-w-lg mx-auto"> <a class="hover:text-blue-500" href="{% url 'leads:category-list' %}">Go back to categories</a> <div class="py-5 border-t border-gray-200"> <h1 class="text-4xl text-gray-800">Create a new category</h1 </div> <form method="post" class="mt-5"> {% csrf_token %} {{ form|crispy }} <button type='submit' class="w-full text-white bg-blue-500 hover:bg-blue-600 px-3 py-2 rounded-md">Submit</button> </form> </div> {% endblock content %}
- Create a category_update.html file in leads/templates/leads
{% extends "base.html" %} {% load tailwind_filters %} {% block content %} <div class="max-w-lg mx-auto"> <a class="hover:text-blue-500" href="{% url 'leads:category-list' %}">Go back to categories</a> <div class="py-5 border-t border-gray-200"> <h1 class="text-4xl text-gray-800">Update {{ object.name }}</h1 </div> <form method="post" class="mt-5"> {% csrf_token %} {{ form|crispy }} <button type='submit' class="w-full text-white bg-blue-500 hover:bg-blue-600 px-3 py-2 rounded-md">Submit</button> </form> </div> {% endblock content %}
- Create a category_delete.html file in leads/templates/leads
{% extends "base.html" %} {% load tailwind_filters %} {% block content %} <div class="max-w-lg mx-auto"> <a class="hover:text-blue-500" href="{% url 'leads:category-list' %}">Go back to categories</a> <div class="py-5 border-t border-gray-200"> <h1 class="text-4xl text-gray-800">Are sure you want to delete {{ object.name }}?</h1 </div> <form method="post" class="mt-5"> {% csrf_token %} {{ form|crispy }} <button type='submit' class="w-full text-white bg-blue-500 hover:bg-blue-600 px-3 py-2 rounded-md">Submit</button> </form> </div> {% endblock content %}
- Edit ./leads/templates/leads/category_list.html
{% extends "base.html" %} {% block content %} <section class="text-gray-600 body-font"> <div class="container px-5 py-24 mx-auto"> <div class="flex flex-col text-center w-full mb-20"> <h1 class="sm:text-4xl text-3xl font-medium title-font mb-2 text-gray-900">Categories</h1> <p class="lg:w-2/3 mx-auto leading-relaxed text-base">These categories segment the leads</p> <a href="{% url 'leads:category-create' %}" class="hover:text-blue-500">Create a category</a> </div> <div class="lg:w-2/3 w-full mx-auto overflow-auto"> <table class="table-auto w-full text-left whitespace-no-wrap"> <thead> <tr> <th class="px-4 py-3 title-font tracking-wider font-medium text-gray-900 text-sm bg-gray-100 rounded-tl rounded-bl">Name</th> <th class="px-4 py-3 title-font tracking-wider font-medium text-gray-900 text-sm bg-gray-100">Lead Count</th> </tr> </thead> <tbody> <tr> <td class="px-4 py-3">Unassigned</td> <td class="px-4 py-3">{{ unassigned_lead_count }}</td> </tr> {% for category in category_list %} <tr> <td class="px-4 py-3"> <a href="{% url 'leads:category-detail' category.pk %}"> {{ category.name }}</a> </td> <td class="px-4 py-3">TODO count</td> </tr> {% endfor %} </tbody> </table> </div> <div class="flex pl-4 mt-4 lg:w-2/3 w-full mx-auto"> <a class="text-indigo-500 inline-flex items-center md:mb-2 lg:mb-0">Learn More <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> <button class="flex ml-auto text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded">Button</button> </div> </div> </section> {% endblock content %}
- Edit the ./leads/urls.py
: from .views import (LeadListView, ... LeadCategoryUpdateView, CategoryCreateView, CategoryUpdateView, CategoryDeleteView) : urlpatterns = [ : path('categories/', CategoryListView.as_view(), name='category-list'), path('categories/<int:pk>/', CategoryDetailView.as_view(), name='category-detail'), path('categories/<int:pk>/update/', CategoryUpdateView.as_view(), name='category-update'), path('categories/<int:pk>/delete/', CategoryDeleteView.as_view(), name='category-delete'), path('create-category/', CategoryCreateView.as_view(), name='category-create'), ]
- Edit crm/leads/templates/leads/category_detail.html
Test 57-1 Compiled in the branch of
{% extends "base.html" %} {% block content %} <section class="text-gray-600 body-font"> <div class="container px-5 py-24 mx-auto"> <div class="flex flex-col text-center w-full mb-20"> <h1 class="sm:text-4xl text-3xl font-medium title-font mb-2 text-gray-900">{{ category.name }}</h1> <p class="lg:w-2/3 mx-auto leading-relaxed text-base">These are the leads under this category</p> <a href="{% url 'leads:category-update' category.pk %}" class="hover:text-blue-500">Update</a> <!--added--> <a href="{% url 'leads:category-delete' category.pk %}" class="hover:text-blue-500">Delete</a> <!--added--> </div> <div class="lg:w-2/3 w-full mx-auto overflow-auto"> <table class="table-auto w-full text-left whitespace-no-wrap"> <thead> <tr> <th class="px-4 py-3 title-font tracking-wider font-medium text-gray-900 text-sm bg-gray-100 rounded-tl rounded-bl">First Name</th> <th class="px-4 py-3 title-font tracking-wider font-medium text-gray-900 text-sm bg-gray-100">Last Name</th> </tr> </thead> <tbody> {% for lead in category.leads.all %} <!-- {% for lead in leads %} --> <tr> <td class="px-4 py-3">{{ lead.first_name }}</td> <td class="px-4 py-3">{{ lead.last_name }}</td> </tr> {% endfor %} </tbody> </table> </div> <div class="flex pl-4 mt-4 lg:w-2/3 w-full mx-auto"> <a class="text-indigo-500 inline-flex items-center md:mb-2 lg:mb-0">Learn More <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" viewBox="0 0 24 24"> <path d="M5 12h14M12 5l7 7-7 7"></path> </svg> </a> <button class="flex ml-auto text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded">Button</button> </div> </div> </section> {% endblock content %}
ver-1.8