• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

liqd / roots / 22072536647

16 Feb 2026 05:41PM UTC coverage: 42.093%. First build
22072536647

Pull #59

github

Pull Request #59: apps/summerization: Integrate Document Summary into Workflow

51 of 314 new or added lines in 7 files covered. (16.24%)

3564 of 8467 relevant lines covered (42.09%)

0.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/apps/summarization/views.py
1
import json
×
2
from types import SimpleNamespace
×
3

NEW
4
from django.conf import settings
×
5
from django.http import HttpResponse
×
6
from django.shortcuts import get_object_or_404
×
NEW
7
from django.shortcuts import render
×
8
from django.views import View
×
NEW
9
from rules.contrib.views import LoginRequiredMixin
×
10

11
from adhocracy4.projects.models import Project
12

×
13
from .export_utils.core import generate_full_export
14
from .playground_errors import format_playground_exception
15
from .pydantic_models import DocumentInputItem
×
16
from .pydantic_models import ProjectSummaryResponse
17
from .services import AIService
18
from .services import SummaryRequest
×
19

×
20

21
def _get_projects_queryset():
22
    """Projects for the test page dropdown (non-draft, ordered)."""
×
23
    return (
NEW
24
        Project.objects.filter(is_draft=False)
×
25
        .select_related("organisation")
26
        .order_by("organisation__name", "name")
27
    )
NEW
28

×
29

NEW
30
def _get_provider_choices():
×
NEW
31
    """Provider choices for summarization test dropdowns from AI_PROVIDERS."""
×
32
    providers = getattr(settings, "AI_PROVIDERS", {})
33
    return [
34
        {
35
            "handle": handle,
NEW
36
            "label": cfg.get("display_name") or handle.replace("_", " ").title(),
×
37
        }
NEW
38
        for handle, cfg in providers.items()
×
NEW
39
    ]
×
NEW
40

×
NEW
41

×
NEW
42
class SummarizationTestExportView(LoginRequiredMixin, View):
×
NEW
43
    """Return project export JSON for the summarization test page."""
×
44

NEW
45
    def get(self, request, project_id):
×
46
        project = get_object_or_404(Project, id=project_id)
NEW
47
        export_data = generate_full_export(project)
×
48
        return HttpResponse(
49
            json.dumps(export_data, indent=2),
50
            content_type="application/json",
NEW
51
        )
×
NEW
52

×
NEW
53

×
54
class SummarizationTestView(View):
55
    """Simple test view for summarization service."""
56

57
    def get(self, request):
NEW
58
        """Display test form."""
×
59
        default_prompt = SummaryRequest.DEFAULT_PROMPT
60
        default_provider = getattr(settings, "AI_PROVIDER", "ovhcloud")
61
        context = {
NEW
62
            "default_prompt": default_prompt,
×
NEW
63
            "projects": _get_projects_queryset(),
×
NEW
64
            "providers": _get_provider_choices(),
×
65
            "provider": default_provider,
NEW
66
        }
×
67
        return render(request, "summarization/test.html", context)
68

69
    def _handle_text_request(
NEW
70
        self, text: str, prompt: str, provider_handle: str | None
×
NEW
71
    ) -> tuple[ProjectSummaryResponse | None, int, str | None]:
×
NEW
72
        """Handle text-only summarization."""
×
73
        try:
74
            service = AIService(provider_handle=provider_handle)
75
            response = service.summarize(
76
                text=text,
NEW
77
                prompt=prompt if prompt else None,
×
NEW
78
                result_type=ProjectSummaryResponse,
×
NEW
79
            )
×
80
            return response, len(text), None
81
        except BaseException as e:
×
82
            if isinstance(e, (KeyboardInterrupt, SystemExit, GeneratorExit)):
83
                raise
×
84
            return None, 0, format_playground_exception(e)
×
85

×
NEW
86
    def _extract_project_from_json(self, text: str):
×
NEW
87
        """Extract project information from JSON text if available."""
×
88
        try:
89
            data = json.loads(text)
×
90
            if isinstance(data, dict) and "project" in data:
91
                project_data = data["project"]
92
                # Create a simple mock project object with needed attributes
93
                project = SimpleNamespace()
94
                project.slug = project_data.get("slug", "test-project")
95
                project.pk = project_data.get("id", 1)
96
                project.result = project_data.get("result", "")
97
                project.name = project_data.get("name", "Test Project")
98

NEW
99
                # Create mock organisation
×
NEW
100
                org = SimpleNamespace()
×
NEW
101
                org_data = project_data.get("organisation", "test-org")
×
NEW
102
                if isinstance(org_data, dict):
×
103
                    org.slug = org_data.get("slug", "test-org")
NEW
104
                else:
×
NEW
105
                    # If organisation is just a string (name), use it as slug
×
106
                    org.slug = str(org_data).lower().replace(" ", "-")
107
                project.organisation = org
NEW
108

×
NEW
109
                # Mock get_absolute_url method
×
NEW
110
                def get_absolute_url():
×
NEW
111
                    return f"/projects/{project.slug}/"
×
NEW
112

×
NEW
113
                project.get_absolute_url = get_absolute_url
×
114

115
                # Mock modules queryset for _summary_section.html
NEW
116
                # The template uses project.modules|get_module_by_id filter
×
NEW
117
                class MockModules:
×
NEW
118
                    def filter(self, id=None):
×
NEW
119
                        return self
×
120

NEW
121
                    def first(self):
×
122
                        return None
123

NEW
124
                project.modules = MockModules()
×
125

126
                return project
NEW
127
        except (json.JSONDecodeError, KeyError, AttributeError):
×
128
            pass
NEW
129
        return None
×
NEW
130

×
131
    def post(self, request):
NEW
132
        """Process summarization request."""
×
133
        text = request.POST.get("text", "")
NEW
134
        prompt = request.POST.get("prompt", "")
×
NEW
135
        provider_handle = request.POST.get("provider", None)
×
NEW
136
        project_id = request.POST.get("project_id", "").strip()
×
NEW
137

×
NEW
138
        default_provider = getattr(settings, "AI_PROVIDER", "ovhcloud")
×
139
        context = {
NEW
140
            "text": text,
×
141
            "prompt": prompt,
142
            "default_prompt": SummaryRequest.DEFAULT_PROMPT,
143
            "provider": provider_handle or default_provider,
144
            "providers": _get_provider_choices(),
145
            "summary_response": None,
146
            "error": None,
147
            "original_length": 0,
NEW
148
            "project": None,
×
149
            "projects": _get_projects_queryset(),
×
NEW
150
        }
×
151

152
        # Use real project from dropdown if project_id was submitted
NEW
153
        if project_id:
×
154
            try:
155
                context["project"] = get_object_or_404(Project, id=int(project_id))
NEW
156
                context["selected_project_id"] = int(project_id)
×
157
            except (ValueError, TypeError):
NEW
158
                pass
×
159
        elif text:
160
            # Fallback: extract mock project from JSON text
161
            project = self._extract_project_from_json(text)
NEW
162
            if project:
×
163
                context["project"] = project
NEW
164

×
165
        if text:
166
            response, original_length, error = self._handle_text_request(
167
                text=text,
168
                prompt=prompt,
NEW
169
                provider_handle=provider_handle,
×
170
            )
171
            context["summary_response"] = response
NEW
172
            context["original_length"] = original_length
×
NEW
173
            context["error"] = error
×
174

175
        return render(request, "summarization/test.html", context)
176

177

×
178
class DocumentSummarizationTestView(View):
NEW
179
    """Test view for document summarization with handles."""
×
NEW
180

×
181
    def get(self, request):
×
182
        """Display test form."""
×
183
        default_provider = getattr(settings, "AI_DOCUMENT_PROVIDER", "ovhcloud")
NEW
184
        context = {
×
185
            "providers": _get_provider_choices(),
NEW
186
            "provider": default_provider,
×
187
        }
188
        return render(request, "summarization/test_documents.html", context)
189

190
    def post(self, request):
191
        """Process document summarization request."""
192
        prompt = request.POST.get("prompt", "")
193
        provider_handle = request.POST.get("provider", None)
194
        if provider_handle == "":
195
            provider_handle = None
196
        documents_json = request.POST.get("documents", "")
197

198
        default_provider = getattr(settings, "AI_DOCUMENT_PROVIDER", "ovhcloud")
199
        context = {
200
            "prompt": prompt,
201
            "provider": provider_handle or default_provider,
202
            "providers": _get_provider_choices(),
203
            "documents_json": documents_json,
204
            "summary_response": None,
205
            "error": None,
206
        }
207

208
        if documents_json:
209
            try:
210
                import json
211

212
                # Parse JSON input
213
                documents_data = json.loads(documents_json)
214

215
                # Convert to list of DocumentInputItem
216
                if isinstance(documents_data, dict):
217
                    # If it's a dict, convert to list format
218
                    documents = [
219
                        DocumentInputItem(handle=handle, url=url)
220
                        for handle, url in documents_data.items()
221
                    ]
222
                elif isinstance(documents_data, list):
223
                    # If it's already a list
224
                    documents = [
225
                        DocumentInputItem(**doc) if isinstance(doc, dict) else doc
226
                        for doc in documents_data
227
                    ]
228
                else:
229
                    raise ValueError("Documents must be a dict or list")
230

231
                # Process documents
232
                service = AIService(document_provider_handle=provider_handle)
233
                response = service.request_vision(
234
                    documents=documents,
235
                    prompt=prompt if prompt else None,
236
                )
237
                context["summary_response"] = response
238

239
            except json.JSONDecodeError as e:
240
                context["error"] = format_playground_exception(e)
241
            except Exception as e:
242
                context["error"] = format_playground_exception(e)
243
        else:
244
            context["error"] = "Please provide documents in JSON format"
245

246
        return render(request, "summarization/test_documents.html", context)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc