scrutiny/scrutiny/projects/views.py

400 lines
13 KiB
Python

import datetime
import json
import re
from django import template
from django.http import Http404, HttpResponse, JsonResponse
from django.shortcuts import render
from django.utils import timezone
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import FormView
from django.views.generic.base import TemplateView
from django.views.generic.detail import DetailView
from .forms import IssueInfoForm
from .models import InstalledService, InstalledVersion, IssueInfo, Module, Project
from .utils import CommitAndIssues, Issue, decorate_commit_line, get_issue_deployment_status
def is_ajax(request):
return request.headers.get('x-requested-with') == 'XMLHttpRequest'
class ProjectDetailView(DetailView):
model = Project
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
platforms = self.object.platform_set.all()
services = self.object.service_set.all()
context['platforms'] = platforms
context['services'] = services
return context
class ModuleDiffView(TemplateView):
template_name = 'projects/diff.html'
def get_context_data(self, name, commit1, commit2, **kwargs):
context = super().get_context_data(**kwargs)
context['module'] = Module.objects.get(name=name)
context['commit1'] = commit1
context['commit2'] = commit2
context['difflog'] = '\n'.join(
[
decorate_commit_line(line)
for line in context['module'].get_diff_log(commit1, commit2).splitlines()
]
)
return context
class ModuleIssuesView(TemplateView):
template_name = 'projects/issues.html'
def get_context_data(self, name, commit1, commit2, **kwargs):
context = super().get_context_data(**kwargs)
context['module'] = Module.objects.get(name=name)
context['commit1'] = commit1
context['commit2'] = commit2
commits = CommitAndIssues.get_for_commits(context['module'], commit1, commit2)
issues = {}
for commit in commits:
for issue in commit.issues:
if not int(issue.id) in issues:
issues[int(issue.id)] = issue
else:
issue = issues[int(issue.id)]
issue.add_commit(commit)
issues_list = list(issues.items())
issues_list.sort()
context['issues'] = [x[1] for x in issues_list]
return context
class ProjectHistoryView(DetailView):
model = Project
template_name_suffix = '_history'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
installed_versions = InstalledVersion.objects.filter(service__platform__project=self.object).order_by(
'-timestamp'
)[:1000]
context['installed_versions'] = installed_versions
return context
class ProjectSummaryHistoryMixin:
# XXX: add an 'interesting' attribute to module model?
interesting_modules = [
'publik-base-theme',
'publik-django-templatetags',
'combo',
'lingo',
'wcs',
'hobo',
'authentic2',
'welco',
'chrono',
'corbo',
'passerelle',
'fargo',
'mandayejs',
'bijoe',
'wcs-olap',
'gadjo',
'godo.js',
]
def get_history(self):
platforms = list(self.object.platform_set.all())
platform = platforms[-1]
installed_versions = list(
InstalledVersion.objects.filter(
timestamp__gt=timezone.now() - datetime.timedelta(days=120),
version__module__name__in=self.interesting_modules,
service__platform=platform,
)
.prefetch_related('version__module')
.exclude(version__version='')
.order_by('-timestamp')[:1000]
)
installed_versions.reverse()
versions_and_day = {}
for installed in installed_versions:
installed_day = installed.timestamp.strftime('%Y-%m-%d')
if installed_day not in versions_and_day:
versions_and_day[installed_day] = {'modules': {}, 'day': installed.timestamp.date()}
if installed.version.module.name in versions_and_day[installed_day]['modules']:
continue
versions_and_day[installed_day]['modules'][installed.version.module.name] = {
'name': installed.version.module.name,
'current_version': installed.version.version,
}
for installed_day in versions_and_day.keys():
versions_and_day[installed_day]['modules'] = {
k: v for k, v in sorted(versions_and_day[installed_day]['modules'].items())
}
versions_sorted_by_day = list(versions_and_day.values())
versions_sorted_by_day.sort(key=lambda x: x.get('day'))
previous_versions = {}
for version_and_day in versions_sorted_by_day:
for module_name, module_versions in list(version_and_day['modules'].items()):
if module_name in previous_versions:
module_versions['previous_version'] = previous_versions[module_name]
if module_versions['previous_version'] == module_versions['current_version']:
del version_and_day['modules'][module_name]
else:
del version_and_day['modules'][module_name]
previous_versions[module_name] = module_versions['current_version']
if len(platforms) > 1:
validation_platform = platforms[-2]
future_versions = {'modules': {}, 'day': 'future'}
module_names = previous_versions.keys()
for module_name in module_names:
installed_version = (
InstalledVersion.objects.filter(
version__module__name=module_name, service__platform=validation_platform
)
.exclude(version__version='')
.select_related('version')
.order_by('-timestamp')
.first()
)
if not installed_version:
continue
if not installed_version.version.version:
continue
if installed_version.version.version == previous_versions[module_name]:
continue
future_versions['modules'][module_name] = {
'name': module_name,
'previous_version': previous_versions[module_name],
'current_version': installed_version.version.version,
}
if future_versions['modules']:
versions_sorted_by_day.append(future_versions)
return versions_sorted_by_day, platform
class IssuesMixin:
def get_issues(self, modules):
modules_by_name = {m.name: m for m in Module.objects.all()}
issues = {}
for module_info in modules.values():
module = modules_by_name[module_info['name']]
if not module.repository_url:
continue
commits = CommitAndIssues.get_for_commits(
module, str(module_info.get('previous_version')), str(module_info.get('current_version'))
)
for commit in commits:
for issue in commit.issues:
if issue.subject == '---': # private issue
continue
if not int(issue.id) in issues:
issues[int(issue.id)] = issue
else:
issue = issues[int(issue.id)]
if not hasattr(issues[int(issue.id)], 'modules'):
issues[int(issue.id)].modules = {}
issues[int(issue.id)].modules[module_info['name']] = True
issues_info = IssueInfo.objects.filter(issue_id__in=issues.keys())
for info in issues_info:
issues[int(info.issue_id)].info = info
issues = list(issues.values())
issues.sort(key=lambda x: int(x.id))
issues.sort(key=lambda x: sorted(x.modules.keys()))
return issues
class ProjectSummaryHistoryView(ProjectSummaryHistoryMixin, DetailView):
model = Project
template_name_suffix = '_summary_history'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
versions_sorted_by_day, platform = self.get_history()
context['platform'] = platform
context['history'] = reversed(versions_sorted_by_day)
return context
class ProjectSummaryHistoryDayView(ProjectSummaryHistoryMixin, IssuesMixin, DetailView):
model = Project
def get(self, request, *args, **kwargs):
self.object = self.get_object()
day = 'future'
if 'year' in kwargs:
day = datetime.date(int(kwargs['year']), int(kwargs['month']), int(kwargs['day']))
versions_sorted_by_day, platform = self.get_history()
version = None
for _version in versions_sorted_by_day:
if _version['day'] == day:
version = _version
break
if not version:
raise Http404
issues = self.get_issues(version['modules'])
return render(
request,
'projects/date_export.html',
{'issues': issues},
)
class ModulesView(TemplateView):
template_name = 'projects/modules.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['modules'] = Module.objects.all().order_by('name')
return context
def try_int(x):
try:
return int(x)
except ValueError:
return 0
version_re = re.compile(r'[\.\+-]d?e?v?')
def key_version(v1):
return [try_int(x) for x in version_re.split(v1.version.version)]
class ModuleDeploymentsView(TemplateView):
template_name = 'projects/deployments.html'
def get_context_data(self, name, **kwargs):
context = super().get_context_data(**kwargs)
context['versions'] = []
context['module'] = Module.objects.get(name=name)
module = context['module']
for service in InstalledService.objects.all():
version = module.get_installed_version(service.platform, service.service)
if version and version not in context['versions']:
if version.version.version:
context['versions'].append(version)
context['versions'].sort(key=key_version)
return context
def module_deployments_json(request, name, **kwargs):
response = HttpResponse(content_type='application/json')
installed_versions = []
module = Module.objects.get(name=name)
for service in InstalledService.objects.all():
version = module.get_installed_version(service.platform, service.service)
if version and version not in installed_versions:
installed_versions.append(version)
json.dump(
[
{
'version': x.version.version,
'project': x.service.platform.project.slug,
'service': x.service.service.slug,
'platform': x.service.platform.slug,
}
for x in installed_versions
],
response,
)
return response
class IssuesSnippet(IssuesMixin, TemplateView):
template_name = 'projects/issues_snippet.html'
@csrf_exempt
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def post(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
modules = json.loads(self.request.read().decode('utf-8'))
issues = self.get_issues(modules)
context['issues'] = issues
return context
class IssueInfoUpdate(FormView):
form_class = IssueInfoForm
template_name = 'projects/issue_info_form.html'
success_url = '/'
def dispatch(self, request, *args, **kwargs):
self.issue = Issue(kwargs['issue_id'])
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
kwargs['issue'] = self.issue
return super().get_context_data(**kwargs)
def get_form_kwargs(self):
info, created = IssueInfo.objects.get_or_create(issue_id=self.issue.id)
kwargs = super().get_form_kwargs()
kwargs['instance'] = info
return kwargs
def form_valid(self, form):
form.save()
self.issue.info = form.instance
if not is_ajax(self.request):
return super().form_valid(form)
tmpl = template.loader.select_template(['projects/issue_fragment.html'])
response = {
'content': tmpl.render({'issue': self.issue}, self.request),
}
return JsonResponse(response)
def api_issues_json(request, *args, **kwargs):
response = HttpResponse(content_type='application/json')
issue_id = kwargs.get('issue')
data = get_issue_deployment_status(issue_id)
json_str = json.dumps(data, indent=2)
for variable in ('jsonpCallback', 'callback'):
if variable in request.GET:
response = HttpResponse(content_type='application/json')
json_str = '%s(%s);' % (request.GET[variable], json_str)
break
response.write(json_str)
return response