Django Integration¶
Full integration with Django including middleware, template tags, management commands, and admin interface.
Installation¶
Install with the Django extra:
pip install civicrm-py[django]
Settings Configuration¶
Add your CiviCRM credentials to Django settings:
# settings.py
CIVICRM_URL = "https://example.org/civicrm/ajax/api4"
CIVICRM_API_KEY = "your-api-key"
# Optional settings
CIVICRM_SITE_KEY = "your-site-key" # For some auth modes
CIVICRM_AUTH_TYPE = "api_key" # api_key, jwt, or basic
CIVICRM_TIMEOUT = 30 # Request timeout in seconds
CIVICRM_VERIFY_SSL = True # SSL certificate verification
CIVICRM_DEBUG = False # Enable debug logging
CIVICRM_MAX_RETRIES = 3 # Maximum retry attempts
For JWT authentication:
CIVICRM_AUTH_TYPE = "jwt"
CIVICRM_JWT_TOKEN = "your-jwt-token"
For basic authentication:
CIVICRM_AUTH_TYPE = "basic"
CIVICRM_USERNAME = "admin"
CIVICRM_PASSWORD = "secret"
Adding to INSTALLED_APPS¶
Add the Django app to enable template tags and management commands:
INSTALLED_APPS = [
# ...
"civicrm_py.contrib.django",
]
Middleware Setup¶
Add the middleware to attach a CiviCRM client to each request:
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
# ...
"civicrm_py.contrib.django.middleware.CiviMiddleware",
]
The middleware automatically detects sync vs async mode:
WSGI deployments get
SyncCiviClientvia thread-local storageASGI deployments get async
CiviClientshared across coroutines
Once configured, access the client via request.civi_client in any view.
Using in Views¶
Sync Views¶
from django.http import JsonResponse
def contact_list(request):
client = request.civi_client
response = client.get("Contact", limit=10)
return JsonResponse({"contacts": response.values})
def contact_detail(request, contact_id):
client = request.civi_client
response = client.get(
"Contact",
where=[["id", "=", contact_id]],
limit=1,
)
if not response.values:
return JsonResponse({"error": "Not found"}, status=404)
return JsonResponse({"contact": response.values[0]})
def contact_create(request):
if request.method != "POST":
return JsonResponse({"error": "POST required"}, status=405)
import json
data = json.loads(request.body)
client = request.civi_client
response = client.create("Contact", data)
return JsonResponse({"contact": response.values[0]}, status=201)
Async Views (Django 4.1+)¶
from django.http import JsonResponse
async def contact_list(request):
client = request.civi_client
response = await client.get("Contact", limit=10)
return JsonResponse({"contacts": response.values})
async def search_contacts(request):
query = request.GET.get("q", "")
client = request.civi_client
response = await client.get(
"Contact",
where=[["display_name", "CONTAINS", query]],
select=["id", "display_name", "email_primary.email"],
limit=25,
)
return JsonResponse({"results": response.values})
Class-Based Views¶
from django.views import View
from django.http import JsonResponse
class ContactListView(View):
def get(self, request):
client = request.civi_client
response = client.get("Contact", limit=10)
return JsonResponse({"contacts": response.values})
Helper Function¶
For explicit error handling, use the helper function:
from civicrm_py.contrib.django.middleware import get_client_from_request
def my_view(request):
try:
client = get_client_from_request(request)
except AttributeError:
return JsonResponse({"error": "CiviCRM not available"}, status=503)
response = client.get("Contact", limit=10)
return JsonResponse({"contacts": response.values})
Context Processor¶
Add the context processor for automatic civi_client availability in all templates:
TEMPLATES = [{
"OPTIONS": {
"context_processors": [
# ...
"civicrm_py.contrib.django.templatetags.civi_tags.civi_context_processor",
],
},
}]
Management Commands¶
Interactive Shell¶
Launch an interactive shell with CiviCRM client pre-configured:
python manage.py civi_shell
The shell provides:
Pre-imported
clientinstanceAuto-completion for entities
History support
Example session:
>>> response = client.get("Contact", limit=5)
>>> for contact in response.values:
... print(contact["display_name"])
Admin Integration¶
The admin integration allows browsing and editing CiviCRM entities through Django admin without Django models.
Setup¶
Create a civiadmin.py file in your app:
# myapp/civiadmin.py
from civicrm_py.contrib.django.admin import (
CiviModelAdmin,
register_entity,
civi_admin_site,
)
from civicrm_py.entities import Contact
@register_entity(Contact, site=civi_admin_site)
class ContactAdmin(CiviModelAdmin):
list_display = ["id", "display_name", "email_primary", "contact_type"]
search_fields = ["display_name", "first_name", "last_name"]
list_filter = ["contact_type", "is_deleted"]
Register the admin site in your URLs:
# urls.py
from django.contrib import admin
from django.urls import path
from civicrm_py.contrib.django.admin import civi_admin_site
urlpatterns = [
path("admin/", admin.site.urls),
path("civiadmin/", civi_admin_site.urls),
]
Admin Components¶
CiviModelAdminBase admin class for CiviCRM entities. Supports list_display, search_fields, list_filter, and pagination.
CiviAdminSiteCustom admin site for CiviCRM data. Separate from Django’s main admin.
CiviInlineAdminInline editing for related entities (e.g., activities on a contact).
CiviQuerySetDjango QuerySet-like wrapper for CiviCRM API queries. Supports filtering, ordering, and pagination.
register_entityDecorator to register entity admin classes.
autodiscover_entitiesAuto-discover
civiadmin.pymodules in installed apps.
Example with Inline Admin¶
from civicrm_py.contrib.django.admin import (
CiviModelAdmin,
CiviInlineAdmin,
register_entity,
civi_admin_site,
)
from civicrm_py.entities import Contact, Activity
class ActivityInline(CiviInlineAdmin):
model = Activity
fk_field = "source_contact_id"
extra = 0
@register_entity(Contact, site=civi_admin_site)
class ContactAdmin(CiviModelAdmin):
list_display = ["id", "display_name", "contact_type"]
inlines = [ActivityInline]
Complete Example¶
Here is a complete example combining middleware, views, and templates.
# settings.py
INSTALLED_APPS = [
# ...
"civicrm_py.contrib.django",
]
MIDDLEWARE = [
# ...
"civicrm_py.contrib.django.middleware.CiviMiddleware",
]
CIVICRM_URL = "https://example.org/civicrm/ajax/api4"
CIVICRM_API_KEY = "your-api-key"
# views.py
from django.shortcuts import render
from django.http import JsonResponse
def contact_list(request):
client = request.civi_client
response = client.get("Contact", limit=25)
return render(request, "contacts/list.html", {
"contacts": response.values,
})
def api_contacts(request):
client = request.civi_client
response = client.get("Contact", limit=10)
return JsonResponse({"contacts": response.values})
{# templates/contacts/list.html #}
{% load civi_tags %}
<h1>Contacts</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Type</th>
</tr>
</thead>
<tbody>
{% for contact in contacts %}
<tr>
<td>{{ contact.display_name }}</td>
<td>{{ contact|civi_field:"email_primary.email" }}</td>
<td>{{ contact.contact_type|civi_contact_type_label }}</td>
</tr>
{% endfor %}
</tbody>
</table>
Cleanup¶
Client cleanup is handled automatically:
Thread-local clients are cleaned up when threads terminate
Async client is cleaned up when the ASGI application shuts down
For explicit cleanup (useful in tests), use:
from civicrm_py.contrib.django.middleware import close_clients, close_clients_async
# In sync code
close_clients()
# In async code
await close_clients_async()