odoo lead creation
This commit is contained in:
parent
b98a507f65
commit
483f076d1a
15 changed files with 404 additions and 27 deletions
|
@ -130,3 +130,35 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
|
|
||||||
MEDIA_URL = "/media/"
|
MEDIA_URL = "/media/"
|
||||||
MEDIA_ROOT = BASE_DIR / "media"
|
MEDIA_ROOT = BASE_DIR / "media"
|
||||||
|
|
||||||
|
|
||||||
|
ODOO_CONFIG = {
|
||||||
|
"url": env.str("ODOO_URL"),
|
||||||
|
"db": env.str("ODOO_DB"),
|
||||||
|
"username": env.str("ODOO_USERNAME"),
|
||||||
|
"password": env.str("ODOO_PASSWORD"),
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGING = {
|
||||||
|
"version": 1,
|
||||||
|
"disable_existing_loggers": False,
|
||||||
|
"formatters": {
|
||||||
|
"verbose": {
|
||||||
|
"format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}",
|
||||||
|
"style": "{",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"handlers": {
|
||||||
|
"console": {
|
||||||
|
"class": "logging.StreamHandler",
|
||||||
|
"formatter": "verbose",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"loggers": {
|
||||||
|
"odoo_api": {
|
||||||
|
"handlers": ["console"],
|
||||||
|
"level": "DEBUG",
|
||||||
|
"propagate": True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
14
hub/services/forms.py
Normal file
14
hub/services/forms.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
from django import forms
|
||||||
|
from .models import Lead
|
||||||
|
|
||||||
|
|
||||||
|
class LeadForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Lead
|
||||||
|
fields = ["name", "company", "email", "phone"]
|
||||||
|
widgets = {
|
||||||
|
"name": forms.TextInput(attrs={"class": "form-control"}),
|
||||||
|
"company": forms.TextInput(attrs={"class": "form-control"}),
|
||||||
|
"email": forms.EmailInput(attrs={"class": "form-control"}),
|
||||||
|
"phone": forms.TextInput(attrs={"class": "form-control"}),
|
||||||
|
}
|
41
hub/services/migrations/0005_lead.py
Normal file
41
hub/services/migrations/0005_lead.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# Generated by Django 5.1.5 on 2025-01-27 15:21
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("services", "0004_cloudprovider_slug"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Lead",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=200)),
|
||||||
|
("company", models.CharField(max_length=200)),
|
||||||
|
("email", models.EmailField(max_length=254)),
|
||||||
|
("phone", models.CharField(max_length=50)),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("odoo_lead_id", models.IntegerField(blank=True, null=True)),
|
||||||
|
(
|
||||||
|
"service",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="services.service",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -107,3 +107,16 @@ class Service(models.Model):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class Lead(models.Model):
|
||||||
|
service = models.ForeignKey(Service, on_delete=models.CASCADE)
|
||||||
|
name = models.CharField(max_length=200)
|
||||||
|
company = models.CharField(max_length=200)
|
||||||
|
email = models.EmailField()
|
||||||
|
phone = models.CharField(max_length=50)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
odoo_lead_id = models.IntegerField(null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name} - {self.company} ({self.service})"
|
||||||
|
|
105
hub/services/odoo.py
Normal file
105
hub/services/odoo.py
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import odoorpc
|
||||||
|
import logging
|
||||||
|
from django.conf import settings
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class OdooAPI:
|
||||||
|
def __init__(self):
|
||||||
|
self.odoo = None
|
||||||
|
self._connect()
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
"""Establish connection to Odoo with detailed error logging"""
|
||||||
|
try:
|
||||||
|
# Parse URL to get host
|
||||||
|
url = settings.ODOO_CONFIG["url"]
|
||||||
|
parsed_url = urlparse(url)
|
||||||
|
host = parsed_url.netloc or parsed_url.path # If no netloc, use path
|
||||||
|
|
||||||
|
# Log connection attempt
|
||||||
|
logger.info(f"Attempting to connect to Odoo at {host}")
|
||||||
|
logger.debug(
|
||||||
|
f"Connection parameters: HOST={host}, DB={settings.ODOO_CONFIG['db']}, "
|
||||||
|
f"USER={settings.ODOO_CONFIG['username']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Try to establish connection
|
||||||
|
self.odoo = odoorpc.ODOO(host, port=443, protocol="jsonrpc+ssl")
|
||||||
|
|
||||||
|
# Try to login
|
||||||
|
logger.info("Connection established, attempting login...")
|
||||||
|
self.odoo.login(
|
||||||
|
settings.ODOO_CONFIG["db"],
|
||||||
|
settings.ODOO_CONFIG["username"],
|
||||||
|
settings.ODOO_CONFIG["password"],
|
||||||
|
)
|
||||||
|
logger.info("Successfully logged into Odoo")
|
||||||
|
|
||||||
|
# Test the connection by making a simple API call
|
||||||
|
version_info = self.odoo.version
|
||||||
|
logger.info(f"Connected to Odoo version: {version_info}")
|
||||||
|
|
||||||
|
except odoorpc.error.RPCError as e:
|
||||||
|
logger.error(f"RPC Error connecting to Odoo: {str(e)}")
|
||||||
|
logger.debug("Full RPC error details:", exc_info=True)
|
||||||
|
raise
|
||||||
|
except odoorpc.error.InternalError as e:
|
||||||
|
logger.error(f"Internal Odoo error: {str(e)}")
|
||||||
|
logger.debug("Full internal error details:", exc_info=True)
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error connecting to Odoo: {str(e)}")
|
||||||
|
logger.debug("Full error details:", exc_info=True)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def create_lead(self, lead):
|
||||||
|
"""Create a lead in Odoo with detailed logging"""
|
||||||
|
try:
|
||||||
|
logger.info(
|
||||||
|
f"Attempting to create lead for {lead.name} from {lead.company}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Prepare lead data
|
||||||
|
lead_data = {
|
||||||
|
"name": f"Interest in {lead.service.name}",
|
||||||
|
"contact_name": lead.name,
|
||||||
|
"partner_name": lead.company,
|
||||||
|
"email_from": lead.email,
|
||||||
|
"phone": lead.phone,
|
||||||
|
"description": f"""
|
||||||
|
Service: {lead.service.name}
|
||||||
|
Provider: {lead.service.cloud_provider.name}
|
||||||
|
Categories: {', '.join(cat.name for cat in lead.service.categories.all())}
|
||||||
|
""",
|
||||||
|
"type": "lead",
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(f"Prepared lead data: {lead_data}")
|
||||||
|
|
||||||
|
# Ensure we have a valid connection
|
||||||
|
if not self.odoo:
|
||||||
|
logger.warning("No active Odoo connection, attempting to reconnect")
|
||||||
|
self._connect()
|
||||||
|
|
||||||
|
# Get the CRM lead model
|
||||||
|
Lead = self.odoo.env["crm.lead"]
|
||||||
|
logger.debug("Successfully got CRM lead model")
|
||||||
|
|
||||||
|
# Create the lead
|
||||||
|
odoo_lead_id = Lead.create(lead_data)
|
||||||
|
logger.info(f"Successfully created lead in Odoo with ID: {odoo_lead_id}")
|
||||||
|
|
||||||
|
return odoo_lead_id
|
||||||
|
|
||||||
|
except odoorpc.error.RPCError as e:
|
||||||
|
logger.error(f"RPC Error creating lead: {str(e)}")
|
||||||
|
logger.debug("Full RPC error details:", exc_info=True)
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error creating lead: {str(e)}")
|
||||||
|
logger.debug("Full error details:", exc_info=True)
|
||||||
|
raise
|
|
@ -1,13 +1,26 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Servala - The Cloud Native Services Hub</title>
|
<title>Services Marketplace</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
.rich-text-content {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
.rich-text-content img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.description-preview img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% block extra_css %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -18,8 +31,8 @@
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link {% if request.resolver_match.view_name == 'services:service_list' %}active{% endif %}"
|
<a class="nav-link {% if request.resolver_match.view_name == 'services:service_list' %}active{% endif %}"
|
||||||
href="{% url 'services:service_list' %}">Services</a>
|
href="{% url 'services:service_list' %}">Services</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,25 +45,5 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
{% block extra_css %}
|
|
||||||
<style>
|
|
||||||
.rich-text-content {
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rich-text-content img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-preview img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
77
hub/services/templates/services/lead_form.html
Normal file
77
hub/services/templates/services/lead_form.html
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
{% extends 'services/base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title mb-4">Show Interest in {{ service.name }}</h2>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<h5>Service Details</h5>
|
||||||
|
<p><strong>Provider:</strong> {{ service.cloud_provider.name }}</p>
|
||||||
|
<p><strong>Service Level:</strong> {{ service.service_level.name }}</p>
|
||||||
|
<p><strong>Price:</strong> ${{ service.price }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{{ message.tags }}">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ form.name.id_for_label }}" class="form-label">Name</label>
|
||||||
|
{{ form.name }}
|
||||||
|
{% if form.name.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{{ form.name.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ form.company.id_for_label }}" class="form-label">Company</label>
|
||||||
|
{{ form.company }}
|
||||||
|
{% if form.company.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{{ form.company.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ form.email.id_for_label }}" class="form-label">Email</label>
|
||||||
|
{{ form.email }}
|
||||||
|
{% if form.email.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{{ form.email.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ form.phone.id_for_label }}" class="form-label">Phone</label>
|
||||||
|
{{ form.phone }}
|
||||||
|
{% if form.phone.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{{ form.phone.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
<a href="{% url 'services:service_detail' service.id %}" class="btn btn-secondary">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -59,6 +59,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<a href="{% url 'services:create_lead' service.id %}" class="btn btn-success">Show Interest</a>
|
||||||
<a href="{% url 'services:service_list' %}" class="btn btn-secondary">Back to Services</a>
|
<a href="{% url 'services:service_list' %}" class="btn btn-secondary">Back to Services</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -113,6 +113,7 @@
|
||||||
</small>
|
</small>
|
||||||
</p>
|
</p>
|
||||||
<a href="{% url 'services:service_detail' service.pk %}" class="btn btn-primary">View Details</a>
|
<a href="{% url 'services:service_detail' service.pk %}" class="btn btn-primary">View Details</a>
|
||||||
|
<a href="{% url 'services:create_lead' service.id %}" class="btn btn-success">Show Interest</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -126,4 +127,4 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
37
hub/services/templates/services/thank_you.html
Normal file
37
hub/services/templates/services/thank_you.html
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
{% extends 'services/base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<h2 class="card-title mb-4">Thank You!</h2>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<p class="lead">Thank you for your interest in {{ service.name }}!</p>
|
||||||
|
<p>We have received your inquiry and our team will contact you shortly.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<h5>Service Details</h5>
|
||||||
|
<p><strong>Provider:</strong> {{ service.cloud_provider.name }}</p>
|
||||||
|
<p><strong>Service Level:</strong> {{ service.service_level.name }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<p class="text-muted">A confirmation email will be sent to your provided email address.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<a href="{% url 'services:service_detail' service.id %}" class="btn btn-primary me-2">
|
||||||
|
Back to Service Details
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'services:service_list' %}" class="btn btn-secondary">
|
||||||
|
Browse More Services
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -6,5 +6,7 @@ app_name = "services"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", views.service_list, name="service_list"),
|
path("", views.service_list, name="service_list"),
|
||||||
path("service/<int:pk>/", views.service_detail, name="service_detail"),
|
path("service/<int:pk>/", views.service_detail, name="service_detail"),
|
||||||
|
path("service/<int:service_id>/interest/", views.create_lead, name="create_lead"),
|
||||||
|
path("service/<int:service_id>/thank-you/", views.thank_you, name="thank_you"),
|
||||||
path("provider/<slug:slug>/", views.provider_detail, name="provider_detail"),
|
path("provider/<slug:slug>/", views.provider_detail, name="provider_detail"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
from django.shortcuts import render, get_object_or_404
|
import logging
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.shortcuts import render, get_object_or_404, redirect
|
||||||
|
from django.contrib import messages
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from .models import Service, CloudProvider, Country, ServiceLevel, Category
|
from .models import Service, CloudProvider, Country, ServiceLevel, Category
|
||||||
|
|
||||||
|
from .forms import LeadForm
|
||||||
|
from .odoo import OdooAPI
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def service_list(request):
|
def service_list(request):
|
||||||
services = Service.objects.all()
|
services = Service.objects.all()
|
||||||
|
@ -59,3 +68,43 @@ def provider_detail(request, slug):
|
||||||
"services": services,
|
"services": services,
|
||||||
}
|
}
|
||||||
return render(request, "services/provider_detail.html", context)
|
return render(request, "services/provider_detail.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
def thank_you(request, service_id):
|
||||||
|
service = get_object_or_404(Service, id=service_id)
|
||||||
|
return render(request, "services/thank_you.html", {"service": service})
|
||||||
|
|
||||||
|
|
||||||
|
def create_lead(request, service_id):
|
||||||
|
service = get_object_or_404(Service, id=service_id)
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
form = LeadForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
lead = form.save(commit=False)
|
||||||
|
lead.service = service
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info(f"Attempting to create lead for service: {service.name}")
|
||||||
|
odoo = OdooAPI()
|
||||||
|
odoo_lead_id = odoo.create_lead(lead)
|
||||||
|
lead.odoo_lead_id = odoo_lead_id
|
||||||
|
lead.save()
|
||||||
|
|
||||||
|
logger.info(f"Successfully created lead with Odoo ID: {odoo_lead_id}")
|
||||||
|
return redirect("services:thank_you", service_id=service.id)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to create lead: {str(e)}", exc_info=True)
|
||||||
|
error_message = "Sorry, there was an error processing your request. Please try again later."
|
||||||
|
|
||||||
|
if settings.DEBUG:
|
||||||
|
error_message += f" Error: {str(e)}"
|
||||||
|
|
||||||
|
messages.error(request, error_message)
|
||||||
|
else:
|
||||||
|
form = LeadForm()
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request, "services/lead_form.html", {"form": form, "service": service}
|
||||||
|
)
|
||||||
|
|
0
odoo_api.log
Normal file
0
odoo_api.log
Normal file
|
@ -8,6 +8,7 @@ dependencies = [
|
||||||
"django>=5.1.5",
|
"django>=5.1.5",
|
||||||
"django-prose-editor[sanitize]>=0.10.3",
|
"django-prose-editor[sanitize]>=0.10.3",
|
||||||
"environs[django]~=14.0",
|
"environs[django]~=14.0",
|
||||||
|
"odoorpc>=0.10.1",
|
||||||
"pillow>=11.1.0",
|
"pillow>=11.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
11
uv.lock
generated
11
uv.lock
generated
|
@ -161,6 +161,15 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/19/31/d65594efd3b42b1de2335d576eb77525691fc320dbf8617948ee05c008e5/nh3-0.2.20-cp38-abi3-win_amd64.whl", hash = "sha256:da87573f03084edae8eb87cfe811ec338606288f81d333c07d2a9a0b9b976c0b", size = 541249 },
|
{ url = "https://files.pythonhosted.org/packages/19/31/d65594efd3b42b1de2335d576eb77525691fc320dbf8617948ee05c008e5/nh3-0.2.20-cp38-abi3-win_amd64.whl", hash = "sha256:da87573f03084edae8eb87cfe811ec338606288f81d333c07d2a9a0b9b976c0b", size = 541249 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "odoorpc"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/cf/0a/9f907fbfefd2486bb4a3faab03f094f03b300b413004f348a3583ecc1898/OdooRPC-0.10.1.tar.gz", hash = "sha256:d0bc524c5b960781165575bad9c13d032d6f968c3c09276271045ddbbb483aa5", size = 58086 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/60/8c5ea2a63151d6c1215e127909eeee3c16e792bbae92ab596dd921d6669d/OdooRPC-0.10.1-py2.py3-none-any.whl", hash = "sha256:a0900bdd5c989c414b1ef40dafccd9363f179312d9166d9486cf70c7c2f0dd44", size = 38482 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "24.2"
|
version = "24.2"
|
||||||
|
@ -214,6 +223,7 @@ dependencies = [
|
||||||
{ name = "django" },
|
{ name = "django" },
|
||||||
{ name = "django-prose-editor", extra = ["sanitize"] },
|
{ name = "django-prose-editor", extra = ["sanitize"] },
|
||||||
{ name = "environs", extra = ["django"] },
|
{ name = "environs", extra = ["django"] },
|
||||||
|
{ name = "odoorpc" },
|
||||||
{ name = "pillow" },
|
{ name = "pillow" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -228,6 +238,7 @@ requires-dist = [
|
||||||
{ name = "django-browser-reload", marker = "extra == 'dev'", specifier = "~=1.13" },
|
{ name = "django-browser-reload", marker = "extra == 'dev'", specifier = "~=1.13" },
|
||||||
{ name = "django-prose-editor", extras = ["sanitize"], specifier = ">=0.10.3" },
|
{ name = "django-prose-editor", extras = ["sanitize"], specifier = ">=0.10.3" },
|
||||||
{ name = "environs", extras = ["django"], specifier = "~=14.0" },
|
{ name = "environs", extras = ["django"], specifier = "~=14.0" },
|
||||||
|
{ name = "odoorpc", specifier = ">=0.10.1" },
|
||||||
{ name = "pillow", specifier = ">=11.1.0" },
|
{ name = "pillow", specifier = ">=11.1.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue