Skip to content
Snippets Groups Projects
Commit efc1e53e authored by Samuel GAIST's avatar Samuel GAIST Committed by Flavio TARSETTI
Browse files

[toolchains][all] Pre-commit cleanup

parent 06fc40c5
No related branches found
No related tags found
2 merge requests!366Cleanup toolchains,!342Django 3 migration
Pipeline #42685 passed
......@@ -29,60 +29,50 @@ from django import forms
from django.contrib import admin
from django.core.files.base import ContentFile
from .models import Toolchain as ToolchainModel
from ..ui.forms import CodeMirrorJSONFileField, CodeMirrorRSTFileField, \
NameField
from ..common.texts import Messages
from ..ui.forms import CodeMirrorJSONFileField
from ..ui.forms import CodeMirrorRSTFileField
from ..ui.forms import NameField
from .models import Toolchain as ToolchainModel
#----------------------------------------------------------
# ----------------------------------------------------------
class ToolchainModelForm(forms.ModelForm):
name = NameField(
widget=forms.TextInput(attrs=dict(size=80)),
help_text=Messages['name'],
widget=forms.TextInput(attrs=dict(size=80)), help_text=Messages["name"],
)
declaration_file = CodeMirrorJSONFileField(
label='Declaration',
help_text=Messages['json'],
label="Declaration", help_text=Messages["json"],
)
description_file = CodeMirrorRSTFileField(
label='Description',
label="Description",
required=False,
allow_empty_file=True,
help_text=Messages['description'],
help_text=Messages["description"],
)
class Meta:
model = ToolchainModel
exclude = []
widgets = {
'short_description': forms.TextInput(
attrs=dict(size=100),
),
'errors': forms.Textarea(
attrs=dict(readonly=1,cols=150,),
),
"short_description": forms.TextInput(attrs=dict(size=100),),
"errors": forms.Textarea(attrs=dict(readonly=1, cols=150,),),
}
def clean_declaration(self):
"""Cleans-up the file data, make sure it is really new"""
new_declaration = self.cleaned_data['declaration_file'].read()
old_declaration = ''
new_declaration = self.cleaned_data["declaration_file"].read()
old_declaration = ""
if self.instance and self.instance.declaration_file.name is not None:
old_declaration = self.instance.declaration_string
if new_declaration == old_declaration:
self.changed_data.remove('declaration_file')
self.changed_data.remove("declaration_file")
content_file = ContentFile(old_declaration)
content_file.name = self.instance.declaration_file.name
return content_file
......@@ -90,55 +80,61 @@ class ToolchainModelForm(forms.ModelForm):
# we don't validate toolchains - they should be saved in any state
# if that works out, then we return the passed file
self.cleaned_data['declaration_file'].seek(0) #reset ContentFile readout
return self.cleaned_data['declaration_file']
self.cleaned_data["declaration_file"].seek(0) # reset ContentFile readout
return self.cleaned_data["declaration_file"]
def clean(self):
"""Cleans-up the input data, make sure it overall validates"""
# make sure we don't pass back a str field as 'file'
if 'declaration_file' in self.data and \
isinstance(self.data['declaration_file'], str):
if "declaration_file" in self.data and isinstance(
self.data["declaration_file"], str
):
mutable_data = self.data.copy()
mutable_data['declaration_file'] = ContentFile(self.data['declaration_file'], name='unsaved')
mutable_data["declaration_file"] = ContentFile(
self.data["declaration_file"], name="unsaved"
)
self.data = mutable_data
#----------------------------------------------------------
# ----------------------------------------------------------
def rehash_toolchain(modeladmin, request, queryset):
"""Recalculates the hash of an toolchain"""
for q in queryset: q.save()
for q in queryset:
q.save()
rehash_toolchain.short_description = 'Rehash selected toolchains'
rehash_toolchain.short_description = "Rehash selected toolchains"
class Toolchain(admin.ModelAdmin):
list_display = ('id',
'author',
'name',
'version',
'short_description',
'creation_date',
'hash',
'previous_version',
'fork_of',
'sharing',
)
search_fields = ['author__username',
'name',
'short_description',
'previous_version__author__username',
'previous_version__name',
'fork_of__name'
]
list_display_links = ('id', 'name')
list_filter = ('sharing', )
readonly_fields = ('hash', 'errors', 'short_description')
list_display = (
"id",
"author",
"name",
"version",
"short_description",
"creation_date",
"hash",
"previous_version",
"fork_of",
"sharing",
)
search_fields = [
"author__username",
"name",
"short_description",
"previous_version__author__username",
"previous_version__name",
"fork_of__name",
]
list_display_links = ("id", "name")
list_filter = ("sharing",)
readonly_fields = ("hash", "errors", "short_description")
actions = [
rehash_toolchain,
......@@ -146,40 +142,32 @@ class Toolchain(admin.ModelAdmin):
form = ToolchainModelForm
filter_horizontal = [
'shared_with',
'shared_with_team'
]
filter_horizontal = ["shared_with", "shared_with_team"]
fieldsets = (
(None,
dict(
fields=('name', 'author'),
),
),
('Documentation',
dict(
classes=('collapse',),
fields=('short_description', 'description_file'),
),
),
('Versioning',
dict(
classes=('collapse',),
fields=('version', 'previous_version', 'fork_of'),
),
),
('Sharing',
dict(
classes=('collapse',),
fields=('sharing', 'shared_with', 'shared_with_team'),
),
),
('Source code',
dict(
fields=('hash', 'declaration_file', 'errors'),
),
),
(None, dict(fields=("name", "author"),),),
(
"Documentation",
dict(
classes=("collapse",), fields=("short_description", "description_file"),
),
),
(
"Versioning",
dict(
classes=("collapse",),
fields=("version", "previous_version", "fork_of"),
),
),
(
"Sharing",
dict(
classes=("collapse",),
fields=("sharing", "shared_with", "shared_with_team"),
),
),
("Source code", dict(fields=("hash", "declaration_file", "errors"),),),
)
admin.site.register(ToolchainModel, Toolchain)
......@@ -26,21 +26,16 @@
###############################################################################
from ..common.api import (
CheckContributionNameView,
ShareView,
ListCreateContributionView,
RetrieveUpdateDestroyContributionView,
)
from ..common.api import CheckContributionNameView
from ..common.api import ListContributionView
from ..common.api import ListCreateContributionView
from ..common.api import RetrieveUpdateDestroyContributionView
from ..common.api import ShareView
from .models import Toolchain
from .serializers import ToolchainSerializer
from .serializers import FullToolchainSerializer
from .serializers import ToolchainCreationSerializer
from .serializers import ToolchainModSerializer
from ..common.api import ListContributionView
from .serializers import ToolchainSerializer
# ----------------------------------------------------------
......
......@@ -25,18 +25,21 @@
# #
###############################################################################
from ..common.apps import CommonAppConfig
from django.utils.translation import ugettext_lazy as _
from ..common.apps import CommonAppConfig
class ToolchainsConfig(CommonAppConfig):
name = 'beat.web.toolchains'
verbose_name = _('Toolchains')
name = "beat.web.toolchains"
verbose_name = _("Toolchains")
def ready(self):
super(ToolchainsConfig, self).ready()
from .signals import auto_delete_file_on_delete, auto_delete_file_on_change
from actstream import registry
registry.register(self.get_model('Toolchain'))
from .signals import auto_delete_file_on_change # noqa: F401
from .signals import auto_delete_file_on_delete # noqa: F401
registry.register(self.get_model("Toolchain"))
......@@ -26,7 +26,6 @@
###############################################################################
import simplejson
from django.conf import settings
from django.db import models
from django.urls import reverse
......@@ -41,8 +40,7 @@ from ..common.models import get_contribution_declaration_filename
from ..common.models import get_contribution_description_filename
from ..common.storage import OverwriteStorage
#----------------------------------------------------------
# ----------------------------------------------------------
def validate_toolchain(declaration):
......@@ -51,39 +49,49 @@ def validate_toolchain(declaration):
toolchain = beat.core.toolchain.Toolchain(settings.PREFIX, declaration)
if not toolchain.valid:
errors = 'The toolchain declaration is **invalid**. Errors:\n * ' + \
'\n * '.join(toolchain.errors)
errors = (
"The toolchain declaration is **invalid**. Errors:\n * "
+ "\n * ".join(toolchain.errors)
)
raise SyntaxError(errors)
return toolchain
#----------------------------------------------------------
# ----------------------------------------------------------
class ToolchainStorage(OverwriteStorage):
def __init__(self, *args, **kwargs):
super(ToolchainStorage, self).__init__(*args, location=settings.TOOLCHAINS_ROOT, **kwargs)
super(ToolchainStorage, self).__init__(
*args, location=settings.TOOLCHAINS_ROOT, **kwargs
)
#----------------------------------------------------------
# ----------------------------------------------------------
class ToolchainManager(StoredContributionManager):
def create_toolchain(self, author, name, short_description='', description='',
declaration=None, version=1, previous_version=None,
fork_of=None):
def create_toolchain(
self,
author,
name,
short_description="",
description="",
declaration=None,
version=1,
previous_version=None,
fork_of=None,
):
# Create the database representation of the toolchain
toolchain = self.model(
author = author,
name = self.model.sanitize_name(name),
version = version,
sharing = self.model.PRIVATE,
previous_version = previous_version,
fork_of = fork_of,
author=author,
name=self.model.sanitize_name(name),
version=version,
sharing=self.model.PRIVATE,
previous_version=previous_version,
fork_of=fork_of,
)
# Check the provided declaration
......@@ -95,11 +103,11 @@ class ToolchainManager(StoredContributionManager):
else:
tc = beat.core.toolchain.Toolchain(settings.PREFIX, data=None)
declaration = tc.data
elif not(isinstance(declaration, dict)):
elif not (isinstance(declaration, dict)):
declaration = simplejson.loads(declaration)
if len(short_description) > 0:
declaration['description'] = short_description
declaration["description"] = short_description
toolchain.declaration = declaration
......@@ -116,18 +124,18 @@ class ToolchainManager(StoredContributionManager):
toolchain.save()
if toolchain.errors:
toolchain.delete() # undo saving to respect current API
toolchain.delete() # undo saving to respect current API
return (None, toolchain.errors)
return (toolchain, None)
#----------------------------------------------------------
# ----------------------------------------------------------
class Toolchain(StoredContribution):
#_____ Constants _______
# _____ Constants _______
DEFAULT_TOOLCHAIN_TEXT = """\
{
"blocks": [],
......@@ -137,68 +145,68 @@ class Toolchain(StoredContribution):
}
"""
#_____ Fields __________
declaration_file = models.FileField(storage=ToolchainStorage(),
upload_to=get_contribution_declaration_filename,
blank=True, null=True,
max_length=200,
db_column='declaration'
)
description_file = models.FileField(storage=ToolchainStorage(),
upload_to=get_contribution_description_filename,
blank=True, null=True,
max_length=200,
db_column='description'
)
# _____ Fields __________
declaration_file = models.FileField(
storage=ToolchainStorage(),
upload_to=get_contribution_declaration_filename,
blank=True,
null=True,
max_length=200,
db_column="declaration",
)
description_file = models.FileField(
storage=ToolchainStorage(),
upload_to=get_contribution_description_filename,
blank=True,
null=True,
max_length=200,
db_column="description",
)
# read-only parameters that are updated at every save(), if required
errors = models.TextField(blank=True, null=True,
help_text="Errors detected while validating the toolchain. Automatically set by the platform.")
errors = models.TextField(
blank=True,
null=True,
help_text="Errors detected while validating the toolchain. Automatically set by the platform.",
)
objects = ToolchainManager()
#_____ Utilities __________
# _____ Utilities __________
def get_absolute_url(self):
return reverse(
'toolchains:view',
args=(self.author.username, self.name, self.version,),
"toolchains:view", args=(self.author.username, self.name, self.version,),
)
def get_api_update_url(self):
'''Returns the endpoint to update this object'''
"""Returns the endpoint to update this object"""
return reverse(
'api_toolchains:object',
args=(self.author.username, self.name, self.version,),
"api_toolchains:object",
args=(self.author.username, self.name, self.version,),
)
def get_api_share_url(self):
'''Returns the endpoint to share this object'''
"""Returns the endpoint to share this object"""
return reverse(
'api_toolchains:share',
args=(self.author.username, self.name, self.version,),
"api_toolchains:share",
args=(self.author.username, self.name, self.version,),
)
def get_new_experiment_url(self):
'''Returns the view to create a new experiment from self'''
"""Returns the view to create a new experiment from self"""
return reverse(
'experiments:new-from-toolchain',
args=(self.author.username, self.name, self.version,),
"experiments:new-from-toolchain",
args=(self.author.username, self.name, self.version,),
)
#_____ Overrides __________
# _____ Overrides __________
def save(self, *args, **kwargs):
......@@ -206,20 +214,24 @@ class Toolchain(StoredContribution):
declaration = self.declaration
# Compute the hash of the content
content_hash = beat.core.hash.hashJSON(declaration, 'description')
content_modified = (content_hash != self.hash)
content_hash = beat.core.hash.hashJSON(declaration, "description")
content_modified = content_hash != self.hash
if content_modified:
# toolchains can be saved even if they are not valid...
wrapper = None
errors = ''
errors = ""
try:
wrapper = validate_toolchain(declaration)
except Exception as e:
errors = str(e)
self.hash = content_hash
self.short_description = wrapper.description if (wrapper is not None) and (wrapper.description is not None) else ''
self.short_description = (
wrapper.description
if (wrapper is not None) and (wrapper.description is not None)
else ""
)
# Store the errors (if applicable)
if errors is not None and not errors.strip():
......@@ -227,7 +239,7 @@ class Toolchain(StoredContribution):
else:
self.errors = errors
else:
self.short_description = declaration.get('description', '')
self.short_description = declaration.get("description", "")
# Ensures that the sharing informations are consistent
if self.sharing == Contribution.USABLE:
......@@ -236,11 +248,10 @@ class Toolchain(StoredContribution):
# Invoke the base implementation
super(Toolchain, self).save(*args, **kwargs)
#_____ Methods __________
# _____ Methods __________
def is_valid(self):
return (self.errors is None)
return self.errors is None
def modifiable(self):
return (self.experiments.count() == 0) and super(Toolchain, self).modifiable()
......
......@@ -27,19 +27,15 @@
from rest_framework import serializers
from ..common.serializers import (
ContributionSerializer,
ContributionCreationSerializer,
ContributionModSerializer,
)
import beat.core.toolchain
from ..attestations.serializers import AttestationSerializer
from ..common.serializers import ContributionCreationSerializer
from ..common.serializers import ContributionModSerializer
from ..common.serializers import ContributionSerializer
from ..experiments.serializers import ExperimentSerializer
from .models import Toolchain
import beat.core.toolchain
# ----------------------------------------------------------
......
......@@ -30,8 +30,7 @@ from django.dispatch import receiver
from .models import Toolchain
#----------------------------------------------------------
# ----------------------------------------------------------
# These two auto-delete files from filesystem when they are unneeded:
......@@ -46,7 +45,7 @@ def auto_delete_file_on_delete(sender, instance, **kwargs):
instance.description_file.delete(save=False)
#----------------------------------------------------------
# ----------------------------------------------------------
@receiver(models.signals.pre_save, sender=Toolchain)
......
......@@ -25,25 +25,22 @@
# #
###############################################################################
import simplejson as json
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.http import Http404
from django.shortcuts import get_object_or_404
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.conf import settings
from django.contrib.auth.models import User
from django.db.models.functions import Coalesce
from beat.core import prototypes
from .models import Toolchain
from ..team.models import Team
from ..reports.models import Report
from ..common.texts import Messages
from ..common.utils import ensure_string
from ..reports.models import Report
from ..team.models import Team
from ..ui.templatetags.markup import restructuredtext
from beat.core import prototypes
import simplejson as json
from .models import Toolchain
@login_required
......@@ -53,22 +50,22 @@ def create(request, name=None):
The user must be authenticated before it can add a new toolchain
"""
parameters = {'toolchain_author': request.user.username,
'toolchain_name': name,
'toolchain_version': 1,
'short_description': '',
'description': '',
'errors': '',
'edition': False,
'messages': Messages,
}
parameters = {
"toolchain_author": request.user.username,
"toolchain_name": name,
"toolchain_version": 1,
"short_description": "",
"description": "",
"errors": "",
"edition": False,
"messages": Messages,
}
# Retrieves the existing toolchain (if necessary)
if name is not None:
previous_versions = Toolchain.objects.filter(
author=request.user,
name__iexact=name,
).order_by('-version')
author=request.user, name__iexact=name,
).order_by("-version")
if len(previous_versions) == 0:
raise Http404()
......@@ -76,19 +73,23 @@ def create(request, name=None):
description = ensure_string(previous_version.description)
parameters['toolchain_version'] = previous_version.version + 1
parameters['declaration'] = previous_version.declaration_string.replace('\n', '')
parameters['short_description'] = previous_version.short_description
parameters['description'] = description.replace('\n', '\\n')
parameters['html_description'] = restructuredtext(description).replace('\n', '')
parameters['errors'] = previous_version.errors.replace('\n', '\\n') if previous_version.errors is not None else ''
parameters["toolchain_version"] = previous_version.version + 1
parameters["declaration"] = previous_version.declaration_string.replace(
"\n", ""
)
parameters["short_description"] = previous_version.short_description
parameters["description"] = description.replace("\n", "\\n")
parameters["html_description"] = restructuredtext(description).replace("\n", "")
parameters["errors"] = (
previous_version.errors.replace("\n", "\\n")
if previous_version.errors is not None
else ""
)
else:
declaration, errors = prototypes.load('toolchain')
parameters['declaration'] = json.dumps(declaration)
declaration, errors = prototypes.load("toolchain")
parameters["declaration"] = json.dumps(declaration)
return render(request,
'toolchains/edition.html',
parameters)
return render(request, "toolchains/edition.html", parameters)
@login_required
......@@ -99,30 +100,30 @@ def fork(request, author, name, version):
"""
# Retrieves the forked toolchain
fork_of = get_object_or_404(Toolchain.objects.for_user(request.user, True),
author__username__iexact=author,
name__iexact=name,
version=int(version)
)
fork_of = get_object_or_404(
Toolchain.objects.for_user(request.user, True),
author__username__iexact=author,
name__iexact=name,
version=int(version),
)
description = ensure_string(fork_of.description)
errors = ensure_string(fork_of.errors)
parameters = {'toolchain_author': request.user.username,
'toolchain_name': name,
'toolchain_version': 1,
'fork_of': fork_of,
'declaration': fork_of.declaration_string.replace('\n', ''),
'short_description': fork_of.short_description,
'description': description.replace('\n', '\\n'),
'errors': errors.replace('\n', '\\n'),
'edition': False,
'messages': Messages,
}
parameters = {
"toolchain_author": request.user.username,
"toolchain_name": name,
"toolchain_version": 1,
"fork_of": fork_of,
"declaration": fork_of.declaration_string.replace("\n", ""),
"short_description": fork_of.short_description,
"description": description.replace("\n", "\\n"),
"errors": errors.replace("\n", "\\n"),
"edition": False,
"messages": Messages,
}
return render(request,
'toolchains/edition.html',
parameters)
return render(request, "toolchains/edition.html", parameters)
@login_required
......@@ -136,29 +137,33 @@ def edit(request, author, name, version):
raise Http404()
# Retrieves the toolchain
toolchain = get_object_or_404(Toolchain,
author__username__iexact=author,
name__iexact=name,
version=int(version)
)
toolchain = get_object_or_404(
Toolchain,
author__username__iexact=author,
name__iexact=name,
version=int(version),
)
description = ensure_string(toolchain.description)
errors = ensure_string(toolchain.errors)
# Render the page
return render(request,
'toolchains/edition.html',
{'toolchain_author': request.user.username,
'toolchain_name': name,
'toolchain_version': toolchain.version,
'declaration': toolchain.declaration_string.replace('\n', ''),
'short_description': toolchain.short_description,
'description': description.replace('\n', '\\n'),
'html_description': restructuredtext(description).replace('\n', ''),
'errors': errors.replace('\n', '\\n'),
'edition': True,
'messages': Messages,
})
return render(
request,
"toolchains/edition.html",
{
"toolchain_author": request.user.username,
"toolchain_name": name,
"toolchain_version": toolchain.version,
"declaration": toolchain.declaration_string.replace("\n", ""),
"short_description": toolchain.short_description,
"description": description.replace("\n", "\\n"),
"html_description": restructuredtext(description).replace("\n", ""),
"errors": errors.replace("\n", "\\n"),
"edition": True,
"messages": Messages,
},
)
def view(request, author, name, version=None):
......@@ -175,8 +180,9 @@ def view(request, author, name, version=None):
version=int(version),
)
else:
toolchain = Toolchain.objects.filter(author__username__iexact=author,
name__iexact=name).order_by('-version')
toolchain = Toolchain.objects.filter(
author__username__iexact=author, name__iexact=name
).order_by("-version")
if not toolchain:
raise Http404()
else:
......@@ -187,25 +193,29 @@ def view(request, author, name, version=None):
if not has_access:
raise Http404()
owner = (request.user == toolchain.author)
owner = request.user == toolchain.author
reports = None
if not request.user.is_anonymous: #fetch user reports, if any
if not request.user.is_anonymous: # fetch user reports, if any
reports = Report.objects.filter(author=request.user)
# Users the object can be shared with
users = User.objects.exclude(username__in=settings.ACCOUNTS_TO_EXCLUDE_FROM_TEAMS).order_by('username')
users = User.objects.exclude(
username__in=settings.ACCOUNTS_TO_EXCLUDE_FROM_TEAMS
).order_by("username")
# Render the page
return render(request,
'toolchains/view.html',
{
'toolchain': toolchain,
'owner': owner,
'reports': reports,
'users': users,
'teams': Team.objects.for_user(request.user, True)
})
return render(
request,
"toolchains/view.html",
{
"toolchain": toolchain,
"owner": owner,
"reports": reports,
"users": users,
"teams": Team.objects.for_user(request.user, True),
},
)
def diff(request, author1, name1, version1, author2, name2, version2):
......@@ -220,7 +230,8 @@ def diff(request, author1, name1, version1, author2, name2, version2):
version=int(version1),
)
has_access, _ = toolchain1.accessibility_for(request.user)
if not has_access: raise Http404()
if not has_access:
raise Http404()
toolchain2 = get_object_or_404(
Toolchain,
......@@ -229,49 +240,47 @@ def diff(request, author1, name1, version1, author2, name2, version2):
version=int(version2),
)
has_access, _ = toolchain2.accessibility_for(request.user)
if not has_access: raise Http404()
if not has_access:
raise Http404()
return render(request,
'toolchains/diff.html',
{
'toolchain1': toolchain1,
'toolchain2': toolchain2,
})
return render(
request,
"toolchains/diff.html",
{"toolchain1": toolchain1, "toolchain2": toolchain2},
)
def ls(request, author_name):
'''List all accessible toolchains to the request user'''
"""List all accessible toolchains to the request user"""
if not author_name: return public_ls(request)
if not author_name:
return public_ls(request)
# check that the user exists on the system
author = get_object_or_404(User, username=author_name)
# orders toolchains so that the latest information is displayed first
objects = Toolchain.objects.from_author_and_public(request.user,
author_name).order_by('-creation_date')
objects = Toolchain.objects.from_author_and_public(
request.user, author_name
).order_by("-creation_date")
objects = Toolchain.filter_latest_versions(objects)
return render(request,
'toolchains/list.html',
dict(
objects=objects,
author=author,
owner=(request.user==author),
))
return render(
request,
"toolchains/list.html",
dict(objects=objects, author=author, owner=(request.user == author),),
)
def public_ls(request):
'''List all publicly accessible objects'''
"""List all publicly accessible objects"""
# orders so that more recent are first
objects = Toolchain.objects.public().order_by('-creation_date')
objects = Toolchain.objects.public().order_by("-creation_date")
objects = Toolchain.filter_latest_versions(objects)
return render(request,
'toolchains/list.html',
dict(
objects=objects,
author=request.user, #anonymous
owner=False,
))
return render(
request,
"toolchains/list.html",
dict(objects=objects, author=request.user, owner=False,), # anonymous
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment