Views (Controller)¶
In Grit, controller is defined as Views.
View files are named views.py and are located in each app directory.
Overview¶
Views act as the controller layer in Grit. They receive HTTP requests, process data using Models, and return HTTP responses (often rendered through Templates).
How Views Connect¶
- Views ↔ Models: Views query and manipulate data through models
- Views ↔ Templates: Views pass context data to templates for rendering HTML
File Location¶
Views are defined in views.py within each app directory:
Two Approaches¶
Grit supports both view patterns:
| Pattern | Best For |
|---|---|
| Function-Based Views (FBV) | Simple logic, one-off handlers, quick prototypes |
| Class-Based Views (CBV) | Reusable patterns, CRUD operations, inheritance |
Authentication¶
Most views require authentication. Use the @login_required decorator:
@login_required
def my_view(request):
# Only authenticated users can access this
return render(request, 'template.html')
Simple Example¶
def hello_world(request):
"""A minimal view that renders a template."""
context = {'message': 'Hello, World!'}
return render(request, 'hello.html', context)
Function-Based Views (FBV)¶
A Function-Based View (FBV) is a Python function that takes a request object and returns a response. This is the most straightforward way to write views.
Basic Structure¶
@login_required
def my_view(request):
# 1. Process the request
# 2. Interact with models (if needed)
# 3. Return a response
return render(request, 'template.html', context)
The Request Object¶
The request object contains everything about the incoming HTTP request:
| Attribute | Description |
|---|---|
request.method |
HTTP method ('GET', 'POST', etc.) |
request.GET |
Query parameters (URL params) |
request.POST |
Form data from POST requests |
request.user |
The authenticated user |
request.session |
Session data dictionary |
Common Return Types¶
# Render a template with context
return render(request, 'app/template.html', {'key': 'value'})
# Redirect to another URL
return redirect('url_name')
return redirect('/path/to/page/')
# Return plain HTTP response
return HttpResponse('Hello World')
Handling GET and POST¶
A common pattern is handling both GET (display form) and POST (process form) in one view:
@login_required
def create_item(request):
if request.method == 'POST':
# Process form submission
name = request.POST.get('name')
Item.objects.create(name=name, owner=request.user)
return redirect('item_list')
# GET request - display the form
return render(request, 'app/create_item.html')
Passing Context to Templates¶
Context is a dictionary of data available in your template:
@login_required
def dashboard(request):
context = {
'user': request.user,
'items': Item.objects.filter(owner=request.user),
'total_count': Item.objects.count(),
}
return render(request, 'app/dashboard.html', context)
When to Use FBV¶
Choose Function-Based Views when: - The logic is simple and specific to one endpoint - You need full control over the request/response flow - The view doesn't fit a standard CRUD pattern - You prefer explicit, readable code over abstraction
Class-Based Views (CBV)¶
A Class-Based View (CBV) is a Python class that handles requests using methods like get() and post(). CBVs provide structure and reusability through inheritance.
Authentication for CBVs¶
For Class-Based Views, use LoginRequiredMixin instead of the decorator:
Note:
LoginRequiredMixinmust come before the view class (e.g.,ListView) in the inheritance order.
Basic Structure¶
class ItemListView(ListView):
model = Item
template_name = 'app/item_list.html'
context_object_name = 'items'
Filtering Data with get_queryset()¶
Override get_queryset() to filter which records are displayed:
class ItemListView(ListView):
model = Item
template_name = 'app/item_list.html'
def get_queryset(self):
# Only show items owned by the current user
return Item.objects.filter(owner=self.request.user)
Adding Context with get_context_data()¶
Override get_context_data() to pass additional data to the template:
class ItemDetailView(DetailView):
model = Item
template_name = 'app/item_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['related_items'] = Item.objects.filter(category=self.object.category)
context['total_count'] = Item.objects.count()
return context
Note: In
DetailView,self.objectis automatically set to the retrieved model instance. You can use it to access the current record's fields.
When to Use CBV¶
Choose Class-Based Views when: - You're building standard CRUD operations (list, detail, create, update, delete) - You want to reuse logic across multiple views through inheritance - The view follows a common pattern that CBVs handle well - You need to organize complex views with multiple methods
Adding Logic to Views¶
Where to Put Business Logic¶
| Location | When to Use |
|---|---|
| In the view | Simple, request-specific logic |
| In model methods | Logic tied to a single model's data |
| In utility functions | Reusable logic shared across views |
Querying the Database¶
Common ORM operations in views:
@login_required
def item_list(request):
# Get all items for the user
items = Item.objects.filter(owner=request.user)
# Get a single item (raises 404 if not found)
item = get_object_or_404(Item, id=item_id, owner=request.user)
# Create a new item
new_item = Item.objects.create(name='New Item', owner=request.user)
# Update an item
item.name = 'Updated Name'
item.save()
# Delete an item
item.delete()
Processing Form Data¶
Getting and validating input from POST requests:
@login_required
def create_item(request):
if request.method == 'POST':
# Get form data
name = request.POST.get('name', '').strip()
description = request.POST.get('description', '')
# Validate
if not name:
return render(request, 'app/create.html', {'error': 'Name is required'})
# Create the item
Item.objects.create(name=name, description=description, owner=request.user)
return redirect('item_list')
return render(request, 'app/create.html')
Conditional Logic¶
Branching based on user or request data:
@login_required
def dashboard(request):
user = request.user
# Show different content based on user type
if user.is_staff:
items = Item.objects.all()
else:
items = Item.objects.filter(owner=user)
# Check query parameters
sort_by = request.GET.get('sort', 'name')
if sort_by == 'date':
items = items.order_by('-created_at')
else:
items = items.order_by('name')
return render(request, 'app/dashboard.html', {'items': items})
Helper Functions¶
Extract reusable logic into separate functions:
# In views.py or a utils.py file
def get_user_items(user, sort_by='name'):
"""Get items for a user with sorting."""
items = Item.objects.filter(owner=user)
if sort_by == 'date':
return items.order_by('-created_at')
return items.order_by('name')
@login_required
def item_list(request):
sort_by = request.GET.get('sort', 'name')
items = get_user_items(request.user, sort_by)
return render(request, 'app/item_list.html', {'items': items})
Error Handling¶
Use try/except for operations that might fail:
@login_required
def delete_item(request, item_id):
try:
item = Item.objects.get(id=item_id, owner=request.user)
item.delete()
return redirect('item_list')
except Item.DoesNotExist:
return render(request, 'app/error.html', {'message': 'Item not found'})
Keep Views Thin¶
Views should coordinate, not contain complex logic. Delegate to models or utilities:
# Bad: Complex logic in the view
@login_required
def process_order(request):
# 50 lines of business logic here...
pass
# Good: Delegate to a service or model method
@login_required
def process_order(request, order_id):
order = get_object_or_404(Order, id=order_id)
order.process() # Logic lives in the model
return redirect('order_detail', order_id=order.id)
Examples from the Codebase¶
Simple FBV: Debug View¶
A minimal view for testing purposes. Shows the basic pattern of building context and rendering.
# From app/views.py
def debug(request):
context = {"data": {}}
context["data"]["response"] = {}
return render(request, "home/debug.html", context)
FBV with Form Handling: CSV Import¶
Demonstrates handling GET/POST, form validation, session storage, and redirects.
# From app/views.py
@login_required
def account_user_data_import_choose_data(request):
"""Step 1: CSV file upload for account user import"""
account_csv_config = CSVColumnConfig(
columns=['user_email', 'user_firstname', 'user_lastname', 'account_name'],
validators={
'user_email': lambda email: '@' in email,
'account_name': lambda name: len(name) <= 255
}
)
if request.method == 'POST':
form = CSVUploadForm(request.POST, request.FILES, column_config=account_csv_config)
if form.is_valid():
csv_data = parse_csv_data(form.cleaned_data['csv_file'], account_csv_config)
request.session['import_data'] = csv_data
return redirect('account_user_data_import_review')
else:
for field_name, errors in form.errors.items():
for error in errors:
messages.error(request, f"{field_name}: {error}")
else:
form = CSVUploadForm(column_config=account_csv_config)
return render(request, 'app/account_user_data_import-choose_data.html', {'form': form})
CBV ListView: Project List¶
Shows how to use get_queryset() to filter data for the current user.
# From chatbot_app/views.py
class DataAutomationProjectListView(ListView):
model = DataAutomationProject
template_name = 'chatbot_app/dataautomationproject_list.html'
context_object_name = 'user_projects'
def get_queryset(self):
# Use custom manager to get projects for current user
return DataAutomationProject.projects.get_user_projects(self.request.user)
CBV DetailView: Project Detail¶
Shows how to add extra context and handle POST requests in a CBV.
# From chatbot_app/views.py
class DataAutomationProjectDetailView(DetailView):
model = DataAutomationProject
template_name = 'chatbot_app/dataautomationproject_detail.html'
context_object_name = 'project'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['invocations'] = DataAutomationInvocation.objects.filter(
data_automation_project=self.object
).order_by('-created_at')
context['clari_form'] = ClariConfigurationForm(
initial=self.object.metadata.get('clari_config', {})
)
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
action = request.POST.get('action')
if action == 'configure_clari':
return self._handle_clari_configuration(request)
return redirect('dataautomationproject_detail', pk=self.object.pk)
Where to Find More Examples¶
| File | Description |
|---|---|
app/views.py |
FBV examples with forms and multi-step workflows |
chatbot_app/views.py |
CBV examples with ListView and DetailView |
core/views.py |
Permission checking and smart redirects |
home/views.py |
Simple page rendering views |