Commit 528af39a authored by CAMPION Sebastien's avatar CAMPION Sebastien

Merge branch '273-merge-webappform-with-userwebappform' into 'django'

Resolve "Merge WebappForm with UserWebappForm"

Closes #273

See merge request !145
parents bd716ef8 ae99af36
Pipeline #54442 failed with stage
in 1 minute and 22 seconds
from allauth.account.forms import SignupForm
from django import forms
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.core.validators import RegexValidator
from django.utils.safestring import mark_safe
......@@ -52,45 +53,6 @@ class SSHForm(forms.ModelForm):
fields = ('sshkey',)
class UserWebappForm(forms.ModelForm):
name = forms.CharField(label_suffix='')
private = forms.BooleanField(required=False, label='Private', label_suffix='')
description = forms.CharField(widget=forms.Textarea, label_suffix='')
contact = forms.EmailField(required=False, label="Contact", label_suffix='')
notebook_gitrepo = forms.URLField(required=False, label="Notebook repository", label_suffix="")
owner = forms.CharField(required=False, label="Owner", label_suffix='', help_text="Username of the new owner of the application. You will immediately loose access to the application.")
tags = TagField(label_suffix='')
memory_limit_mb = forms.IntegerField(label="Memory limit", label_suffix='',
min_value=0)
job_queue = forms.ModelChoiceField(
queryset=JobQueue.objects.all().distinct().order_by("timeout"),
label='Default job queue',
label_suffix='')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
webapp = Webapp.objects.get(docker_name=self.instance.docker_name)
self.fields['owner'].initial = self.instance.user
self.fields['memory_limit_mb'].initial = (
self.instance.memory_limit // 1024**2
or config.env.ALLGO_WEBAPP_DEFAULT_MEMORY_LIMIT_MB)
def get_memory_limit(self, request):
# only allgo admins are allowed to change the memory limit
if request.user.is_superuser:
# value is converted into bytes
return self.cleaned_data["memory_limit_mb"] * 1024**2
else:
return self.instance.memory_limit
class Meta:
model = Webapp
fields = ('name', 'description', 'contact', 'notebook_gitrepo',
'private', 'owner', 'docker_os', 'tags', 'entrypoint',
'job_queue')
class HomeSignupForm(SignupForm):
def __init__(self, *args, **kwargs):
......@@ -180,6 +142,8 @@ class WebappForm(forms.ModelForm):
widget=forms.RadioSelect)
# Advanced
ADVANCED_FIELDS = ("docker_os", "memory_limit_mb", "job_queue",
"entrypoint", "owner", "tags")
docker_os = forms.ModelChoiceField(
queryset=DockerOs.objects.all().distinct(),
label='Operating sytem',
......@@ -195,10 +159,17 @@ class WebappForm(forms.ModelForm):
entrypoint = forms.CharField(label="Entrypoint", label_suffix="",
help_text=mark_safe('This is the <a href="https://allgo.gitlabpages.inria.fr/doc/deploy.html#entrypoint">command executed when allgo runs a job</a> for this app.'),
initial="/home/allgo/entrypoint")
owner = forms.CharField(required=False, label="Owner", label_suffix='', help_text="Username of the new owner of the application. You will immediately loose access to the application.")
tags = TagField(required=False, label_suffix='', help_text="Tags are separated by a comma.")
def __init__(self, *args, **kwargs):
super(WebappForm, self).__init__(*args, **kwargs)
try:
self.fields['owner'].initial = self.instance.user
except ObjectDoesNotExist:
pass
self.instance.job_queue = JobQueue.objects.filter(
is_default=True).first()
......@@ -216,7 +187,7 @@ class WebappForm(forms.ModelForm):
class Meta:
model = Webapp
fields = ('name', 'description', 'logo_file_name', 'contact', 'entrypoint', 'job_queue', 'private', 'docker_os', 'entrypoint')
fields = ('name', 'description', 'contact', 'entrypoint', 'job_queue', 'private', 'docker_os', 'entrypoint', 'owner', 'tags')
class WebappSandboxForm(forms.ModelForm):
......
......@@ -61,7 +61,6 @@ from allauth.account.models import EmailAddress
from .forms import (
UserForm,
HomeSignupForm,
UserWebappForm,
JobForm,
SSHForm,
RunnerForm,
......@@ -214,14 +213,14 @@ class WebappUpdate(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
success_message: message when the form is properly submitted.
"""
form_class = UserWebappForm
template_name = 'webapp_update.html'
form_class = WebappForm
template_name = 'webapp_add_update.html'
success_message = 'Your app has been successfully updated.'
error_message = 'The email doesn\'t belong to any registered user. Please enter a valid owner email address.'
def get_success_url(self):
"""If successful redirect to the same page"""
return reverse('main:webapp_update', args=(self.object.docker_name,))
"""If successful redirect to the webapp details page"""
return reverse('main:webapp_detail', args=(self.object.docker_name,))
def get_object(self):
"""Returns the object according to its docker name or a 404 error"""
......@@ -229,6 +228,16 @@ class WebappUpdate(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
queryset = get_object_or_404(Webapp, docker_name=data, user_id=self.request.user.id)
return queryset
def get_context_data(self, **kwargs):
ctx=super().get_context_data(**kwargs)
ctx["action"] = "Update"
# we expand the 'advanced' tab if any of its field has a validation error
ctx["show_advanced"] = bool(set(ctx["form"].errors)
.intersection(ctx["form"].ADVANCED_FIELDS))
return ctx
def get_form(self):
form = super().get_form()
if not self.request.user.is_superuser:
......@@ -238,6 +247,7 @@ class WebappUpdate(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
def form_valid(self, form):
"""Save data coming from the form in the database """
obj = form.save(commit=False)
try:
user = User.objects.get(username=form.cleaned_data['owner'])
obj.user_id = user.id
......@@ -250,10 +260,10 @@ class WebappUpdate(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
messages.success(self.request, self.success_message)
return redirect('main:user_webapp_list', self.request.user.username)
else:
return super(WebappUpdate, self).form_valid(form)
return super().form_valid(form)
except User.DoesNotExist:
messages.error(self.request, self.error_message)
return super(WebappUpdate, self).form_invalid(form)
return super().form_invalid(form)
class WebappCreate(SuccessMessageMixin, LoginRequiredMixin, IsProviderMixin, CreateView):
......@@ -270,7 +280,7 @@ class WebappCreate(SuccessMessageMixin, LoginRequiredMixin, IsProviderMixin, Cre
model = Webapp
form_class = WebappForm
success_message = 'Webapp created successfully.'
template_name = 'webapp_add.html'
template_name = 'webapp_add_update.html'
# group_required = ['inria', ]
def get_success_url(self):
......@@ -283,6 +293,12 @@ class WebappCreate(SuccessMessageMixin, LoginRequiredMixin, IsProviderMixin, Cre
form.fields['memory_limit_mb'].widget.attrs['readonly'] = True
return form
def get_context_data(self, **kwargs):
ctx=super().get_context_data(**kwargs)
ctx["action"] = "Create"
ctx["show_advanced"] = False
return ctx
def form_valid(self, form):
"""Save data coming from the form in the database """
obj = form.save(commit=False)
......
{% extends "base.html" %}
{% load htmlattrs converters %}
{% block title %}Create a webapp{% endblock %}
{% block title %}
{{action}} a webapp
{% endblock %}
{% block breadcrumb %}
<li class="breadcrumb-item"><a href="{% url 'main:webapp_list' %}">Applications</a></li>
<li class="breadcrumb-item active" aria-current="page">Create</li>
<li class="breadcrumb-item active" aria-current="page">{{action}}</li>
{% endblock %}
{% block content %}
<div class="container">
<div class="allgo-page">
<ul class="nav nav-tabs">
<li class="nav-item"><a class="nav-link active" data-toggle="tab" href="#basic">Basic</a></li>
<li class="nav-item"><a class="nav-link" data-toggle="tab" href="#advanced">Advanced</a></li>
</ul>
<p>You can create a new application here by giving a name.</p>
<form method="post" class="clearfix">
{% csrf_token %}
<div class="tab-content">
<div class="tab-pane active" id="basic">
<div class="form-row">
<div class="col">
<div class="form-group">
......@@ -44,6 +34,8 @@
{{ form.contact | attr:"placeholder:john.smith@example.net" | add_class:"form-control" }}
<small class="form-text text-muted">{{ form.contact.help_text }}</small>
</div>
</div>
</div>
......@@ -57,7 +49,7 @@
{{ form.private.label_tag }}
<div class="btn-group btn-group-toggle" data-toggle="buttons">
{% for private_field in form.private %}
<label class="btn btn-secondary">
<label class="btn btn-secondary {% if private_field.data.selected %}active{% endif %}">
<input
type="{{ private_field.data.type }}"
id="{{ private_field.id_for_label }}"
......@@ -71,10 +63,16 @@
<small class="form-text text-muted">{{ form.private.help_text }}</small>
</div>
</div>
<div class="tab-pane" id="advanced">
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#collapseAdvanced" aria-expanded="{{show_advanced}}" aria-controls="collapseAdvanced">
Advanced options
</button>
</h5>
</div>
<div class="collapse {% if show_advanced %}show{% endif %}" id="collapseAdvanced">
<div class="card-body">
<div class="form-row">
<div class="form-group col">
......@@ -109,6 +107,12 @@
{% endfor %}
</select>
<small class="form-text text-muted">{{ form.job_queue.help_text }}</small>
<div class="form-group">
{{ form.tags.label_tag }}
{{ form.tags | attr:"placeholder:comma, separated, tags" | add_class:"form-control" }}
<small class="form-text text-muted">{{ form.tags.help_text }}</small>
</div>
</div>
<div class="form-group col-md-6">
......@@ -117,12 +121,22 @@
{{ form.entrypoint | add_class:"form-control" }}
</div>
<small class="form-text text-muted">{{ form.entrypoint.help_text }}</small>
{% if action != "Create" %}
<div class="form-group">
{{ form.owner.label_tag }}
{{ form.owner | attr:"placeholder:Enter the new owner username" | add_class:"form-control" }}
<small class="form-text text-muted">{{ form.owner.help_text }}</small>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<input class="btn btn-primary float-right" type="submit" value="Create webapp">
<input class="btn btn-primary float-right mt-4" type="submit" value="{{action}} webapp">
</form>
</div>
......@@ -135,3 +149,14 @@
{{ block.super }}
{% include 'partials/_form_messages.html' %}
{% endblock %}
{% block javascript %}
{{ block.super }}
<script>
$("#collapseAdvanced input").on("invalid", function(ev) {
{# unfold the 'Advanced options' panel if any of its field triggers an 'invalid' value event #}
$(ev.target).closest(".collapse").collapse("show")
});
</script>
{% endblock %}
......@@ -27,11 +27,33 @@
href="{% url 'main:webapp_update' webapp.docker_name %}"
data-toggle="tooltip"
data-placement="top"
title="Update the application">
title="Edit properties">
<i class="fas fa-square"></i>
<i class="fa-inverse fas fa-pencil-alt" data-fa-transform="shrink-7 down-.25 left-.25"></i>
<span class="text-hide">Update the application</span>
<span class="text-hide">Edit properties</span>
</a>
<a class="fa-layers fa-2x"
href="{% url 'main:webapp_sandbox_panel' webapp.docker_name %}"
data-toggle="tooltip"
data-placement="top"
title="Create new version">
<i class="fas fa-square"></i>
<i class="fa-inverse fas fa-plus" data-fa-transform="shrink-7 down-.25 left-.25"></i>
<span class="text-hide">Create a new version</span>
</a>
{% if webapp.imported %}
<a class="fa-layers fa-2x"
href="{% url 'main:webapp_version_import' webapp.docker_name %}"
data-toggle="tooltip"
data-placement="top"
title="Import version">
<i class="fas fa-square"></i>
<i class="fa-inverse fas fa-cloud-download-alt" data-fa-transform="shrink-7 down-.25 left-2"></i>
<span class="text-hide">Import version</span>
</a>
{% endif %}
{% endif %}
</div>
</div>
......
{% extends "base.html" %}
{% load i18n static htmlattrs converters %}
{% block title %}Update your webapp{% endblock %}
{% block breadcrumb %}
<li class="breadcrumb-item"><a href="{% url 'main:webapp_list' %}">Applications</a></li>
<li class="breadcrumb-item"><a href="{% url 'main:webapp_detail' webapp.docker_name %}">{{ webapp.name | fancy_webapp_name | title }}</a></li>
<li class="breadcrumb-item active" aria-current="page">Update</li>
{% endblock %}
{% block content %}
<div class="container">
<div class="allgo-page">
<div class="row">
<div class="col-10">
<h1 class="page-title">
<span>Application profile</span>
<span class="float-right">
<a class="btn btn-primary"
href="{% url 'main:webapp_sandbox_panel' webapp.docker_name %}">Create a new version</a>
{% if webapp.imported %}
<a class="btn btn-primary"
href="{% url 'main:webapp_version_import' webapp.docker_name %}"><i class="fas fa-cloud-download-alt"></i> Import versions</a>
</span>
{% endif %}
</h1>
<div class="row">
<div class="col">
<form method="post">
{% csrf_token %}
<div class="form-group">
{{ form.name.label_tag }}
{{ form.name | attr:"placeholder:Name of your app" | add_class:"form-control" }}
</div>
<div class="form-group">
{{ form.owner.label_tag }}
{{ form.owner | attr:"placeholder:Enter the new owner username" | add_class:"form-control" }}
<small class="form-text text-muted">{{ form.owner.help_text }}</small>
</div>
<div class="form-group">
{{ form.contact.label_tag }}
{{ form.contact | attr:"placeholder:Enter the contact email" | add_class:"form-control" }}
</div>
<div class="form-group">
{{ form.description.label_tag }}
{{ form.description | attr:"placeholder:Enter the description of your app here" | add_class:"form-control" }}
</div>
<div class="form-group">
{{ form.entrypoint.label_tag }}
<div class="input-group">
{{ form.entrypoint | add_class:"form-control" }}
</div>
</div>
<div class="form-group">
{{ form.docker_os.label_tag }}
<select name="{{ form.docker_os.name }}" id="{{ form.docker_os.id_for_label }}" class="form-control">
{% for choice in form.docker_os.field.queryset %}
<option value="{{ choice.pk }}">{{ choice.name }} {{ choice.version }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
{{ form.notebook_gitrepo.label_tag }}
{{ form.notebook_gitrepo | attr:"placeholder:https://example.tld/repo.git" | add_class:"form-control" }}
</div>
<div class="form-group">
{{ form.job_queue.label_tag }}
<select name="{{ form.job_queue.name }}" id="{{ form.job_queue.id_for_label }}" class="form-control">
{% for choice in form.job_queue.field.queryset %}
<option value="{{ choice.pk }}"
{% if choice.pk == form.instance.job_queue_id %} selected {% endif %}
>{{ choice | fancy_job_queue }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<div class="form-check">
{{ form.private | add_class:"form-check-input" }}
{{ form.private.label_tag }}
</div>
</div>
{% comment %}
{{ form.tags.label_tag }}
<select class="js-example-basic-multiple custom-select webapp-tags-update" size="3" name="tags" multiple="multiple">
{% for tag in form.tags %}
<option value="{{ tag.data.value }}" {% if tag.data.selected %}selected="selected"{% endif %}>{{ tag.data.label }}</option>
{% endfor %}
</select>
<a href="{% url 'main:tag_add' webapp.docker_name %}" title="Add a tag"><i class="fas fa-plus"></i><span class="text-hide">Add a tag</span></a>
{% endcomment %}
<div class="form-group">
{{ form.tags.label_tag }}
{{ form.tags | attr:"placeholder:comma, serparated, tags" | add_class:"form-control" }}
</div>
<div class="form-group">
{{ form.memory_limit_mb.label_tag }}
<div class="input-group">
{{ form.memory_limit_mb | attr:"placeholder:Memory limit" | add_class:"form-control" }}
<div class="input-group-append">
<span class="input-group-text">MB</span>
</div>
</div>
</div>
<input class="btn btn-primary" type="submit" value="Update">
</form>
</div>
<div class="col">
<form method="post">
<div class="form-group">
<label>Application picture</label>
<p><img src="{% static 'images/identicon.png' %}" width="50%" /></p>
</div>
<input class="btn btn-secondary" type="submit" value="Upload a new picture">
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block messages %}
{{ block.super }}
{% include 'partials/_form_messages.html' %}
{% endblock %}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment