Commit a73c29d1 authored by Stefano Alberto Russo's avatar Stefano Alberto Russo
Browse files

Merge branch 'feature/editable_models' into develop

parents 08649091 d42f7baf
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
# Generated by Django 2.2.1 on 2025-07-16 08:37

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
        ('core_app', '0037_storage_read_only'),
    ]

    operations = [
        migrations.AddField(
            model_name='computing',
            name='user',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='computings', to=settings.AUTH_USER_MODEL),
        ),
        migrations.AddField(
            model_name='storage',
            name='user',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='storages', to=settings.AUTH_USER_MODEL),
        ),
    ]
+87 −19
Original line number Diff line number Diff line
@@ -19,6 +19,20 @@ class ConfigurationError(Exception):
class ConsistencyError(Exception):
    pass

def get_computing_manager(computing):
    from . import computing_managers
    # Hash table mapping
    managers_mapping = {}
    managers_mapping['cluster'+'ssh+cli'+'user_keys'+'slurm'] = computing_managers.SlurmSSHClusterComputingManager
    managers_mapping['standalone'+'ssh+cli'+'user_keys'+'None'] = computing_managers.SSHStandaloneComputingManager
    managers_mapping['standalone'+'ssh+cli'+'platform_keys'+'None'] = computing_managers.SSHStandaloneComputingManager
    managers_mapping['standalone'+'internal'+'internal'+'None'] = computing_managers.InternalStandaloneComputingManager

    try:
        return  managers_mapping[computing.type+computing.access_mode+computing.auth_mode+str(computing.wms)](computing)
    except KeyError:
        raise ValueError('No computing manager defined for type="{}", access_mode="{}", auth_mode="{}", wms="{}"'
                         .format(computing.type, computing.access_mode, computing.auth_mode, computing.wms)) from None

# Setup logging
import logging
@@ -153,10 +167,22 @@ class Container(models.Model):
        return str('Container "{}" of user "{}" with image name "{}" and image tag "{}" on registry "{}" '.format(self.name, user_str, self.image_name, self.image_tag, self.registry))

    def save(self, *args, **kwargs):
        # Check that digest starts with sha256:

        if self.image_tag is None:
            raise ValueError('The image tag field must be always set (e.g. using "latest")')
        if not self.image_arch:
            self.image_arch = None
        if not self.image_os:
            raise ValueError('The image must be always set')
        if self.image_digest and not self.image_digest.startswith('sha256:'):
            raise ValueError('The digest field must start with "sha256:"')

        if not self.interface_port:
            self.interface_port = None
        if not self.interface_protocol:
            self.interface_protocol = None
        if not self.interface_transport:
            self.interface_transport = None
        super(Container, self).save(*args, **kwargs)

    @property
@@ -171,6 +197,11 @@ class Container(models.Model):
        color_map_index = string_int_hash % len(color_map)
        return color_map[color_map_index]

    @property
    def env_vars_json(self):
        if not self.env_vars:
            return''
        return json.dumps(self.env_vars)



@@ -181,8 +212,10 @@ class Container(models.Model):
class Computing(models.Model):

    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey(User, related_name='computings', on_delete=models.CASCADE, blank=True, null=True)
    # If a computing has no user, it will be available to anyone. Can be created, edited and deleted only by admins.
    group = models.ForeignKey(Group, related_name='computings', on_delete=models.CASCADE, blank=True, null=True)
    # If a compute resource has no group, it will be available to anyone. Can be created, edited and deleted only by admins.
    # If a computing resource has no group, it will be available to anyone. Can be created, edited and deleted only by admins.

    name        = models.CharField('Name', max_length=255, blank=False, null=False)
    description = models.TextField('Description', blank=True, null=True)
@@ -220,6 +253,25 @@ class Computing(models.Model):
        else:
            return str('Computing "{}"'.format(self.name))


    def save(self, *args, **kwargs):

        if not self.container_engines:
            self.container_engines = None
        if not self.supported_archs:
            self.supported_archs = None
        if not self.emulated_archs:
            self.emulated_archs = None
        if not self.conf:
            self.conf = None

        try:
            get_computing_manager(self)
        except:
            raise
            raise ValueError('Unsupported combination of type, access_mode, auth_mode, and wms')
        super(Computing, self).save(*args, **kwargs)

    @property
    def uuid_as_str(self):
        return str(self.uuid)
@@ -234,6 +286,29 @@ class Computing(models.Model):
    def default_container_engine(self):
        return self.container_engines[0]

    @property
    def container_engines_json(self):
        if not self.container_engines:
            return''
        return json.dumps(self.container_engines)

    @property
    def supported_archs_json(self):
        if not self.supported_archs:
            return ''
        return json.dumps(self.supported_archs)

    @property
    def emulated_archs_json(self):
        if not self.emulated_archs:
            return ''
        return json.dumps(self.emulated_archs)

    @property
    def conf_json(self):
        if not self.conf:
            return ''
        return json.dumps(self.conf)

    #=======================
    # Computing manager
@@ -241,29 +316,15 @@ class Computing(models.Model):

    @property
    def manager(self):
        from . import computing_managers

        # Hash table mapping
        managers_mapping = {}
        managers_mapping['cluster'+'ssh+cli'+'user_keys'+'slurm'] = computing_managers.SlurmSSHClusterComputingManager
        managers_mapping['standalone'+'ssh+cli'+'user_keys'+'None'] = computing_managers.SSHStandaloneComputingManager
        managers_mapping['standalone'+'ssh+cli'+'platform_keys'+'None'] = computing_managers.SSHStandaloneComputingManager
        managers_mapping['standalone'+'internal'+'internal'+'None'] = computing_managers.InternalStandaloneComputingManager

        # Instantiate the computing manager and return (if not already done)
        try:
            return self._manager
        except AttributeError:
            try:
                self._manager = managers_mapping[self.type+self.access_mode+self.auth_mode+str(self.wms)](self)
            except KeyError:
                raise ValueError('No computing resource manager for type="{}", access_mode="{}", auth_mode="{}", wms="{}"'
                                 .format(self.type, self.access_mode, self.auth_mode, self.wms)) from None
            else:
            self._manager = get_computing_manager(self)
        return self._manager



#=========================
#  Tasks
#=========================
@@ -371,7 +432,10 @@ class Task(models.Model):
class Storage(models.Model):

    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey(User, related_name='storages', on_delete=models.CASCADE, blank=True, null=True)
    # If a storage has no user, it will be available to anyone. Can be created, edited and deleted only by admins.
    group = models.ForeignKey(Group, related_name='storages', on_delete=models.CASCADE, blank=True, null=True)
    # If a comstorageputing has no user, it will be available to anyone. Can be created, edited and deleted only by admins.

    name = models.CharField('Name', max_length=255, blank=False, null=False)
    #description = models.TextField('Description', blank=True, null=True)
@@ -419,7 +483,11 @@ class Storage(models.Model):
    def id(self):
        return (self.name if not self.computing else '{}:{}'.format(self.computing.name,self.name))


    @property
    def conf_json(self):
        if not self.conf:
            return ''
        return json.dumps(self.conf)



+118 −0
Original line number Diff line number Diff line
{% load static %}
{% include "header.html" %}
{% include "navigation.html" %}
{% include "logo.html" %}

<div class="container">
  <div class="dashboard">
    <div class="span8 offset2">
      <h1><a href="/computing">Computing resources</a> <span style="font-size:18px"> / add</span></h1>
      <hr>

      {% if data.error %}
        <div class="alert alert-danger">{{ data.error }}</div>
      {% endif %}
      {% if data.added %}
        <div class="alert alert-success">Resource added successfully.</div>
      {% endif %}

      <form action="#" method="POST">
      {% csrf_token %}
      <table class="dashboard" style="width:500px; margin-bottom:25px">
        <tr>
          <td><b>Name</b></td>
          <td><input type="text" name="name" value="" size="30" required /></td>
        </tr>
        <tr>
          <td><b>Owner</b></td>
          <td>
            <select name="user_id" style="width: 220px;">
              <option value="{{ data.user.id }}">{{ data.user.email }}</option>
              {% if data.user.is_staff %}<option value="">Platform</option>{% endif %}
            </select>
          </td>
        </tr>
        <tr>
          <td><b>Description</b></td>
          <td><textarea name="description" rows="3" cols="30"></textarea></td>
        </tr>
        <tr>
          <td><b>Group</b></td>
          <td>
            <select name="group_id" style="width: 220px;">
              <option value="">None</option>
              {% for group in data.groups %}
                <option value="{{ group.id }}">{{ group.name }}</option>
              {% endfor %}
            </select>
          </td>
        </tr>
        <tr>
          <td><b>Type</b></td>
          <td>
            <select name="type" style="width: 220px;" required>
              <option value="cluster">cluster</option>
              <option value="standalone">standalone</option>
            </select>
          </td>
        </tr>
        <tr>
          <td><b>Arch</b></td>
          <td><input type="text" name="arch" value="" size="30" required /></td>
        </tr>
        <tr>
          <td><b>Access mode</b></td>
          <td>
            <select name="access_mode" style="width: 220px;" required>
              <option value="ssh+cli">ssh+cli</option>
              <option value="internal">internal</option>
            </select>
          </td>
        </tr>
        <tr>
          <td><b>Auth mode</b></td>
          <td>
            <select name="auth_mode" style="width: 220px;" required>
              <option value="user_keys">user_keys</option>
              <option value="platform_keys">platform_keys</option>
              <option value="internal">internal</option>
            </select>
          </td>
        </tr>
        <tr>
          <td><b>WMS</b></td>
          <td>
            <select name="wms" style="width: 220px;">
              <option value="">None</option>
              <option value="slurm">slurm</option>
            </select>
          </td>
        </tr>
        <tr>
          <td><b>Container engines</b><br/><span style="font-size:0.8em">(JSON list)</span></td>
          <td><textarea name="container_engines" rows="2" cols="30" required></textarea></td>
        </tr>
        <tr>
          <td><b>Supported archs</b><br/><span style="font-size:0.8em">(JSON list)</span></td>
          <td><textarea name="supported_archs" rows="2" cols="30"></textarea></td>
        </tr>
        <tr>
          <td><b>Emulated archs</b><br/><span style="font-size:0.8em">(JSON dict)</span></td>
          <td><textarea name="emulated_archs" rows="2" cols="30"></textarea></td>
        </tr>
        <tr>
          <td><b>Conf</b><br/><span style="font-size:0.8em">(JSON dict)</span></td>
          <td><textarea name="conf" rows="2" cols="30"></textarea></td>
        </tr>
      </table>
      <table style="width:500px; border:0; background:#ffffff; margin-top:20px">
        <tr><td align="center">
          <input type="submit" value="Create" class="btn btn-primary">
        </td></tr>
      </table>
      </form>
    </div>
  </div>
</div>

{% include "footer.html" %} 
 No newline at end of file
+57 −38
Original line number Diff line number Diff line
@@ -6,83 +6,94 @@
<div class="container">
  <div class="dashboard">
    <div class="span8 offset2">
      <h1>Add software container</h1>
      <hr>
      <h1><a href="/software">Software containers</a> <span style="font-size:18px"> / add</span></h1>
      <br/>

      {% if not data.added %}

          Here you can add a new software container on the platform. You can add containers from image registries
          <!--  Here you can add a new software container on the platform. You can add containers from image registries
          as <a href="https://hub.docker.com/">Docker Hub </a>or by importing Git repositories, provided that they
          are compatible with <a href="https://mybinder.readthedocs.io/en/latest/introduction.html">Binder</a> specifications.
          <br/>
          <br/>
          -->

          {% if data.new_container_from == 'registry' %}
          <div style="font-size:1.1em; background:whitesmoke; display:inline-block; padding:2px 15px 2px 15px">New container from registry</div>
          <div style="font-size:1.1em; background:white; display:inline-block; padding:2px 15px 2px 15px"><a href="?new_container_from=repository">New container from Git repository</a></div>
          <div style="font-size:1.1em; background:whitesmoke; display:inline-block; padding:2px 15px 2px 15px">From registry</div>
          <div style="font-size:1.1em; background:white; display:inline-block; padding:2px 15px 2px 15px"><a href="?new_container_from=repository">From repository</a></div>
          <hr style="margin-top:0;">

          <h4>Basics</h4>

          <form action="#" method="POST">
          {% csrf_token %}

          <table class="dashboard" style="width:400px; margin-bottom:25px">
          <table class="dashboard" style="width:500px; margin-bottom:25px">

           <tr>
            <td><b>Name</b></td>
            <td>
             <input type="text" name="container_name" value="" placeholder="" size="23" required />
             <input type="text" name="container_name" value="" placeholder="" size="36" required />
            </td>
           </tr>

           <tr>
            <td><b>Description</b></td>
            <td>
             <!-- ><input type="text" name="container_description" value="" placeholder="" size="23" required /> -->
<textarea name="container_description" rows="3" cols="22"></textarea>
             <textarea name="container_description" rows="3" cols="35"></textarea>
            </td>
           </tr>
           <tr>
            <td><b>Group</b></td>
            <td>
              <select name="group_id" style="width: 220px;">
                <option value="">None</option>
                {% for group in data.groups %}
                  <option value="{{ group.id }}">{{ group.name }}</option>
                {% endfor %}
              </select>
            </td>
           </tr>
          </table>

          <!-- <h4>Registry & image</h4> -->
          <table class="dashboard" style="width:500px; margin-bottom:25px">

           <tr>
            <td><b>Registry</b></td><td>
             <input type="text" name="container_registry" value="docker.io" size="23" required />
             <input type="text" name="container_registry" value="docker.io" size="36" required />
            </td>
           </tr>

           <tr>
            <td><b>Image&nbsp;name</b></td>
            <td>
             <input type="text" name="container_image_name" value="" placeholder="" size="23" required />
             <input type="text" name="container_image_name" value="" placeholder="" size="36" required />
            </td>
           </tr>

           <tr>
            <td><b>Image tag</b></td>
            <td>
             <input type="text" name="container_image_tag" value="latest" size="23" required />
             <input type="text" name="container_image_tag" value="latest" size="36" required />
            </td>
           </tr>

          </table>



          <h4>Interface </h4>
          <table class="dashboard" style="width:400px; margin-bottom:25px">
          <!-- <h4>Interface</h4> -->
          <table class="dashboard" style="width:500px; margin-bottom:25px">

           <tr>
            <td><b>Interface port</b></td>
             <td><input type="text" name="container_interface_port" value="" placeholder="" size="5" /></td>
            <td style="width:180px"><b>Interface port</b></td>
             <td><input type="text" name="container_interface_port" value="" placeholder="" size="6" /></td>
           </tr>

           <tr>
            <td><b>Interface protocol</b></td>
            <td style="width:180px"><b>Interface protocol</b></td>
            <td>
             {% if request.user.profile.is_power_user %}
             <input type="text" value="http" name="container_interface_protocol" size="5" />
             <input type="text" value="http" name="container_interface_protocol" size="6" />
             {% else %}
             <select name="container_interface_protocol" >
             <select name="container_interface_protocol" style="width:65px">
             <option value="http" selected>http</option>
             <option value="https">https</option>
             </select>
@@ -171,35 +182,37 @@


          {% else %}
          <div style="font-size:1.1em; background:white; display:inline-block; padding:2px 15px 2px 15px"><a href="?new_container_from=registry">New container from registry</a></div>
          <div style="font-size:1.1em; background:whitesmoke; display:inline-block; padding:2px 15px 2px 15px">New container from Git repository</div>
          <div style="font-size:1.1em; background:white; display:inline-block; padding:2px 15px 2px 15px"><a href="?new_container_from=registry">From registry</a></div>
          <div style="font-size:1.1em; background:whitesmoke; display:inline-block; padding:2px 15px 2px 15px">From repository</div>
          <hr style="margin-top:0;">

          <h4>Basics</h4>

          <form action="#" method="POST">
          {% csrf_token %}

          <table class="dashboard" style="width:400px; margin-bottom:25px">
          <table class="dashboard" style="width:500px; margin-bottom:25px">

           <tr>
            <td><b>Name</b></td>
            <td>
             <input type="text" name="container_name" value="" placeholder="" size="23" required />
             <input type="text" name="container_name" value="" placeholder="" size="36" required />
            </td>
           </tr>

           <tr>
            <td><b>Description</b></td>
            <td>
             <!-- ><input type="text" name="container_description" value="" placeholder="" size="23" required /> -->
<textarea name="container_description" rows="3" cols="22"></textarea>
             <textarea name="container_description" rows="3" cols="35"></textarea>
            </td>
           </tr>

          </table>

          <!-- <h4>Repository</h4> -->
          <table class="dashboard" style="width:500px; margin-bottom:25px">

           <tr>
            <td><b>Repository URL</b></td><td>
             <input type="text" name="repository_url" size="23" required />
            <td><b>Repository URL*</b></td><td>
             <input type="text" name="repository_url" size="35" required />
            </td>
           </tr>

@@ -207,26 +220,32 @@
           <tr>
            <td><b>Repository tag</b></td>
            <td>
             <input type="text" name="repository_tag" placeholder="Tag or hash, optional" value="" size="23" />
             <input type="text" name="repository_tag" placeholder="Tag or hash, optional" value="" size="35" />
            </td>
           </tr>

           <tr>
            <td colspan=2>
            *must comply with the <a href="https://mybinder.readthedocs.io/en/latest/introduction.html">Binder</a> specifications.
            </td>
           </tr>
          </table>



          <table style="width:400px; border:0; background:#ffffff; margin-top:20px">
          <tr><td align="center">
          <input type="submit" value="Add">
          </td></tr>
          </table>



          <input type="hidden" name="new_container_from" value="repository">

          </form>




          {% endif %}


+100 −0
Original line number Diff line number Diff line
{% load static %}
{% include "header.html" %}
{% include "navigation.html" %}
{% include "logo.html" %}

<div class="container">
  <div class="dashboard">
    <div class="span8 offset2">
      <h1><a href="/storage/?manage=True">Storage resources</a> <span style="font-size:18px">/ add</span></h1>
      <hr>

      {% if data.error %}
        <div class="alert alert-danger">{{ data.error }}</div>
      {% endif %}
      {% if data.added %}
        <div class="alert alert-success">Storage added successfully.</div>
      {% endif %}

      <form action="#" method="POST">
      {% csrf_token %}
      <table class="dashboard" style="width:500px; margin-bottom:25px">
        <tr>
          <td><b>Name</b></td>
          <td><input type="text" name="name" value="" size="30" required /></td>
        </tr>
        <tr>
          <td><b>Owner</b></td>
          <td>
            <select name="user_id" style="width: 220px;">
              <option value="{{ data.user.id }}">{{ data.user.email }}</option>
              {% if data.user.is_staff %}<option value="">Platform</option>{% endif %}
            </select>
          </td>
        </tr>
        <tr>
          <td><b>Group</b></td>
          <td>
            <select name="group_id" style="width: 220px;">
              <option value="">None</option>
              {% for group in data.groups %}
                <option value="{{ group.id }}">{{ group.name }}</option>
              {% endfor %}
            </select>
          </td>
        </tr>
        <tr>
          <td><b>Type</b></td>
          <td><input type="text" name="type" value="" size="30" required /></td>
        </tr>
        <tr>
          <td><b>Access mode</b></td>
          <td><input type="text" name="access_mode" value="" size="30" required /></td>
        </tr>
        <tr>
          <td><b>Auth mode</b></td>
          <td><input type="text" name="auth_mode" value="" size="30" required /></td>
        </tr>
        <tr>
          <td><b>Base path</b></td>
          <td><input type="text" name="base_path" value="" size="30" required /></td>
        </tr>
        <tr>
          <td><b>Bind path</b></td>
          <td><input type="text" name="bind_path" value="" size="30" /></td>
        </tr>
        <tr>
          <td><b>Computing</b></td>
          <td>
            <select name="computing_uuid" style="width: 220px;">
              <option value="">None</option>
              {% for computing in data.computings %}
                <option value="{{ computing.uuid }}">{{ computing.name }}</option>
              {% endfor %}
            </select>
          </td>
        </tr>
        <tr>
          <td><b>Read only</b></td>
          <td><input type="checkbox" name="read_only" value="True" /></td>
        </tr>
        <tr>
          <td><b>Browsable</b></td>
          <td><input type="checkbox" name="browsable" value="True" checked /></td>
        </tr>
        <tr>
          <td><b>Conf</b><br/><span style="font-size:0.8em">(JSON dict)</span></td>
          <td><textarea name="conf" rows="2" cols="30"></textarea></td>
        </tr>
      </table>
      <table style="width:500px; border:0; background:#ffffff; margin-top:20px">
        <tr><td align="center">
          <input type="submit" value="Create" class="btn btn-primary">
        </td></tr>
      </table>
      </form>
    </div>
  </div>
</div>

{% include "footer.html" %} 
 No newline at end of file
Loading