Skip to content

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

URL Request → View → Model (data) → Template (presentation) → Response
  • 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:

app_name/
├── views.py      ← Controllers live here
├── models.py
├── urls.py
└── templates/

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:

class ItemListView(LoginRequiredMixin, ListView):
    model = Item
    template_name = 'app/item_list.html'

Note: LoginRequiredMixin must 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.object is 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