Enforce unique Project.name for same user and unique Session.name for same user and project

This commit is contained in:
Dana Van Aken 2019-10-08 05:03:11 -04:00
parent 40c75de3ce
commit c568c09c00
6 changed files with 96 additions and 37 deletions

View File

@ -8,7 +8,6 @@ Created on Jul 25, 2017
@author: dvanaken @author: dvanaken
''' '''
from django import forms from django import forms
from .models import Session, Project, Hardware, SessionKnob from .models import Session, Project, Hardware, SessionKnob
@ -25,6 +24,24 @@ class NewResultForm(forms.Form):
class ProjectForm(forms.ModelForm): class ProjectForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user_id = kwargs.pop('user_id')
self.project_id = kwargs.pop('project_id')
super().__init__(*args, **kwargs)
def is_valid(self):
valid = super().is_valid()
if valid:
new_name = self.cleaned_data['name']
user_projects = Project.objects.filter(
user__id=self.user_id, name=new_name)
if self.project_id:
user_projects = user_projects.exclude(id=self.project_id)
if user_projects.exists():
valid = False
self._errors['name'] = ["Project '{}' already exists.".format(new_name)]
return valid
class Meta: # pylint: disable=old-style-class,no-init class Meta: # pylint: disable=old-style-class,no-init
model = Project model = Project
@ -50,7 +67,11 @@ class SessionForm(forms.ModelForm):
storage_type = forms.ChoiceField(label='Storage Type', choices=StorageType.choices()) storage_type = forms.ChoiceField(label='Storage Type', choices=StorageType.choices())
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(SessionForm, self).__init__(*args, **kwargs) self.project_id = kwargs.pop('project_id')
self.user_id = kwargs.pop('user_id')
self.session_id = kwargs.pop('session_id')
super().__init__(*args, **kwargs)
self.fields['description'].required = False self.fields['description'].required = False
self.fields['target_objective'].required = False self.fields['target_objective'].required = False
self.fields['tuning_session'].required = True self.fields['tuning_session'].required = True
@ -59,8 +80,21 @@ class SessionForm(forms.ModelForm):
self.fields['storage'].initial = 32 self.fields['storage'].initial = 32
self.fields['storage_type'].initial = StorageType.SSD self.fields['storage_type'].initial = StorageType.SSD
def is_valid(self):
valid = super().is_valid()
if valid:
new_name = self.cleaned_data['name']
user_sessions = Session.objects.filter(
user__id=self.user_id, project__id=self.project_id, name=new_name)
if self.session_id:
user_sessions = user_sessions.exclude(id=self.session_id)
if user_sessions.exists():
valid = False
self._errors['name'] = ["Session '{}' already exists.".format(new_name)]
return valid
def save(self, commit=True): def save(self, commit=True):
model = super(SessionForm, self).save(commit=False) model = super().save(commit=False)
cpu2 = self.cleaned_data['cpu'] cpu2 = self.cleaned_data['cpu']
memory2 = self.cleaned_data['memory'] memory2 = self.cleaned_data['memory']
@ -99,7 +133,7 @@ class SessionKnobForm(forms.ModelForm):
name = forms.CharField(max_length=128) name = forms.CharField(max_length=128)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(SessionKnobForm, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['session'].required = False self.fields['session'].required = False
self.fields['knob'].required = False self.fields['knob'].required = False
self.fields['name'].widget.attrs['readonly'] = True self.fields['name'].widget.attrs['readonly'] = True

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2019-10-02 07:59 # Generated by Django 1.10.1 on 2019-10-08 03:47
from __future__ import unicode_literals from __future__ import unicode_literals
from django.conf import settings from django.conf import settings
@ -150,9 +150,6 @@ class Migration(migrations.Migration):
('last_update', models.DateTimeField()), ('last_update', models.DateTimeField()),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
], ],
options={
'abstract': False,
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='Result', name='Result',
@ -186,16 +183,13 @@ class Migration(migrations.Migration):
('creation_time', models.DateTimeField()), ('creation_time', models.DateTimeField()),
('last_update', models.DateTimeField()), ('last_update', models.DateTimeField()),
('upload_code', models.CharField(max_length=30, unique=True)), ('upload_code', models.CharField(max_length=30, unique=True)),
('tuning_session', models.CharField(choices=[('tuning_session', 'Tuning Session'), ('no_tuning_session', 'No Tuning'), ('randomly_generate', 'Randomly Generate')], default='tuning_session', max_length=64)), ('tuning_session', models.CharField(choices=[('tuning_session', 'Tuning Session'), ('no_tuning_session', 'No Tuning'), ('randomly_generate', 'Randomly Generate')], default='tuning_session', max_length=64, verbose_name='session type')),
('target_objective', models.CharField(choices=[('throughput_txn_per_sec', 'Throughput'), ('99th_lat_ms', '99 Percentile Latency')], max_length=64, null=True)), ('target_objective', models.CharField(choices=[('throughput_txn_per_sec', 'Throughput'), ('99th_lat_ms', '99 Percentile Latency')], max_length=64, null=True)),
('dbms', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='website.DBMSCatalog')), ('dbms', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='website.DBMSCatalog')),
('hardware', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='website.Hardware')), ('hardware', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='website.Hardware')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='website.Project')), ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='website.Project')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
], ],
options={
'abstract': False,
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='SessionKnob', name='SessionKnob',
@ -264,6 +258,14 @@ class Migration(migrations.Migration):
name='workload', name='workload',
unique_together=set([('dbms', 'hardware', 'name')]), unique_together=set([('dbms', 'hardware', 'name')]),
), ),
migrations.AlterUniqueTogether(
name='session',
unique_together=set([('user', 'project', 'name')]),
),
migrations.AlterUniqueTogether(
name='project',
unique_together=set([('user', 'name')]),
),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name='pipelinedata', name='pipelinedata',
unique_together=set([('pipeline_run', 'task_type', 'workload')]), unique_together=set([('pipeline_run', 'task_type', 'workload')]),

View File

@ -164,6 +164,9 @@ class Project(BaseModel):
x.delete() x.delete()
super(Project, self).delete(using, keep_parents) super(Project, self).delete(using, keep_parents)
class Meta: # pylint: disable=old-style-class,no-init
unique_together = ('user', 'name')
class Hardware(BaseModel): class Hardware(BaseModel):
@ -201,13 +204,14 @@ class Session(BaseModel):
last_update = models.DateTimeField() last_update = models.DateTimeField()
upload_code = models.CharField(max_length=30, unique=True) upload_code = models.CharField(max_length=30, unique=True)
TUNING_OPTIONS = [ TUNING_OPTIONS = OrderedDict([
("tuning_session", "Tuning Session"), ("tuning_session", "Tuning Session"),
("no_tuning_session", "No Tuning"), ("no_tuning_session", "No Tuning"),
("randomly_generate", "Randomly Generate") ("randomly_generate", "Randomly Generate")
] ])
tuning_session = models.CharField(choices=TUNING_OPTIONS, tuning_session = models.CharField(choices=TUNING_OPTIONS.items(),
max_length=64, default='tuning_session') max_length=64, default='tuning_session',
verbose_name="session type")
TARGET_OBJECTIVES = [ TARGET_OBJECTIVES = [
('throughput_txn_per_sec', 'Throughput'), ('throughput_txn_per_sec', 'Throughput'),
@ -228,6 +232,9 @@ class Session(BaseModel):
r.delete() r.delete()
super(Session, self).delete(using=DEFAULT_DB_ALIAS, keep_parents=False) super(Session, self).delete(using=DEFAULT_DB_ALIAS, keep_parents=False)
class Meta: # pylint: disable=old-style-class,no-init
unique_together = ('user', 'project', 'name')
class SessionKnobManager(models.Manager): class SessionKnobManager(models.Manager):
@staticmethod @staticmethod

View File

@ -13,7 +13,8 @@
<th>{{ labels.dbms }}</th> <th>{{ labels.dbms }}</th>
<th>{{ labels.hardware }}</th> <th>{{ labels.hardware }}</th>
<th>{{ labels.tuning_session }}</th> <th>{{ labels.tuning_session }}</th>
<th>{{ labels.creation_time }}</th> <th>{{ labels.algorithm }}</th>
<!-- <th>{{ labels.creation_time }}</th> -->
<th>{{ labels.last_update }}</th> <th>{{ labels.last_update }}</th>
</tr> </tr>
{% for session in sessions %} {% for session in sessions %}
@ -22,8 +23,9 @@
<td style="vertical-align:middle"><a href="{% url 'session' project.pk session.pk %}">{{ session.name }}</a></td> <td style="vertical-align:middle"><a href="{% url 'session' project.pk session.pk %}">{{ session.name }}</a></td>
<td style="vertical-align:middle">{{ session.dbms.full_name }}</td> <td style="vertical-align:middle">{{ session.dbms.full_name }}</td>
<td style="vertical-align:middle">{{ session.hardware }}</td> <td style="vertical-align:middle">{{ session.hardware }}</td>
<td style="vertical-align:middle">{{ session.tuning_session }}</td> <td style="vertical-align:middle">{{ session.session_type_name }}</td>
<td style="vertical-align:middle">{{ session.creation_time }}</td> <td style="vertical-align:middle">{{ session.algorithm_name }}</td>
<!-- <td style="vertical-align:middle">{{ session.creation_time }}</td> -->
<td style="vertical-align:middle">{{ session.last_update }}</td> <td style="vertical-align:middle">{{ session.last_update }}</td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -89,9 +89,13 @@ caption span {float: right;}
<td><div class="text-right">{{ labels.hardware }}</div></td> <td><div class="text-right">{{ labels.hardware }}</div></td>
<td>{{ session.hardware }}</td> <td>{{ session.hardware }}</td>
</tr> </tr>
<tr>
<td><div class="text-right">{{ labels.tuning_session }}</div></td>
<td>{{ session.session_type_name }}</td>
</tr>
<tr> <tr>
<td><div class="text-right">{{ labels.algorithm }}</div></td> <td><div class="text-right">{{ labels.algorithm }}</div></td>
<td>{{ algorithm_name }}</td> <td>{{ session.algorithm_name }}</td>
</tr> </tr>
<tr> <tr>
<td><div class="text-right">{{ labels.creation_time }}</div></td> <td><div class="text-right">{{ labels.creation_time }}</div></td>
@ -101,10 +105,6 @@ caption span {float: right;}
<td><div class="text-right">{{ labels.last_update }}</div></td> <td><div class="text-right">{{ labels.last_update }}</div></td>
<td>{{ session.last_update }}</td> <td>{{ session.last_update }}</td>
</tr> </tr>
<tr>
<td><div class="text-right">{{ labels.tuning_session }}</div></td>
<td>{{ session.tuning_session }}</td>
</tr>
<tr> <tr>
<td><div class="text-right">{{ labels.target_objective }}</div></td> <td><div class="text-right">{{ labels.target_objective }}</div></td>
<td>{{ metric_meta|get_item:session.target_objective|get_attr:"pprint" }}</td> <td>{{ metric_meta|get_item:session.target_objective|get_attr:"pprint" }}</td>

View File

@ -141,9 +141,10 @@ def home_projects_view(request):
@login_required(login_url=reverse_lazy('login')) @login_required(login_url=reverse_lazy('login'))
def create_or_edit_project(request, project_id=''): def create_or_edit_project(request, project_id=''):
form_kwargs = dict(user_id=request.user.pk, project_id=project_id)
if request.method == 'POST': if request.method == 'POST':
if project_id == '': if project_id == '':
form = ProjectForm(request.POST) form = ProjectForm(request.POST, **form_kwargs)
if not form.is_valid(): if not form.is_valid():
return render(request, 'edit_project.html', {'form': form}) return render(request, 'edit_project.html', {'form': form})
project = form.save(commit=False) project = form.save(commit=False)
@ -151,22 +152,24 @@ def create_or_edit_project(request, project_id=''):
ts = now() ts = now()
project.creation_time = ts project.creation_time = ts
project.last_update = ts project.last_update = ts
project.save()
else: else:
project = get_object_or_404(Project, pk=project_id, user=request.user) project = get_object_or_404(Project, pk=project_id, user=request.user)
form = ProjectForm(request.POST, instance=project) form_kwargs.update(instance=project)
form = ProjectForm(request.POST, **form_kwargs)
if not form.is_valid(): if not form.is_valid():
return render(request, 'edit_project.html', {'form': form}) return render(request, 'edit_project.html', {'form': form})
project.last_update = now() project.last_update = now()
project.save()
project.save()
return redirect(reverse('project_sessions', kwargs={'project_id': project.pk})) return redirect(reverse('project_sessions', kwargs={'project_id': project.pk}))
else: else:
if project_id == '': if project_id == '':
project = None project = None
form = ProjectForm() form = ProjectForm(**form_kwargs)
else: else:
project = Project.objects.get(pk=int(project_id)) project = Project.objects.get(pk=project_id)
form = ProjectForm(instance=project) form_kwargs.update(instance=project)
form = ProjectForm(**form_kwargs)
context = { context = {
'project': project, 'project': project,
'form': form, 'form': form,
@ -191,6 +194,10 @@ def project_sessions_view(request, project_id):
'button_create': 'create a new session', 'button_create': 'create a new session',
})) }))
form_labels['title'] = "Your Sessions" form_labels['title'] = "Your Sessions"
for session in sessions:
session.session_type_name = Session.TUNING_OPTIONS[session.tuning_session]
session.algorithm_name = AlgorithmType.name(session.algorithm)
context = { context = {
"sessions": sessions, "sessions": sessions,
"project": project, "project": project,
@ -245,8 +252,12 @@ def session_view(request, project_id, session_id):
knobs = SessionKnob.objects.get_knobs_for_session(session) knobs = SessionKnob.objects.get_knobs_for_session(session)
knob_names = [knob["name"] for knob in knobs if knob["tunable"]] knob_names = [knob["name"] for knob in knobs if knob["tunable"]]
session.session_type_name = Session.TUNING_OPTIONS[session.tuning_session]
session.algorithm_name = AlgorithmType.name(session.algorithm)
form_labels = Session.get_labels() form_labels = Session.get_labels()
form_labels['title'] = "Session Info" form_labels['title'] = "Session Info"
context = { context = {
'project': project, 'project': project,
'dbmss': dbmss, 'dbmss': dbmss,
@ -263,7 +274,6 @@ def session_view(request, project_id, session_id):
'knob_names': knob_names, 'knob_names': knob_names,
'filters': [], 'filters': [],
'session': session, 'session': session,
'algorithm_name': AlgorithmType.TYPE_NAMES[session.algorithm],
'results': results, 'results': results,
'labels': form_labels, 'labels': form_labels,
} }
@ -274,13 +284,14 @@ def session_view(request, project_id, session_id):
@login_required(login_url=reverse_lazy('login')) @login_required(login_url=reverse_lazy('login'))
def create_or_edit_session(request, project_id, session_id=''): def create_or_edit_session(request, project_id, session_id=''):
project = get_object_or_404(Project, pk=project_id, user=request.user) project = get_object_or_404(Project, pk=project_id, user=request.user)
form_kwargs = dict(user_id=request.user.pk, project_id=project_id, session_id=session_id)
if request.method == 'POST': if request.method == 'POST':
if not session_id: if not session_id:
# Create a new session from the form contents # Create a new session from the form contents
form = SessionForm(request.POST) form = SessionForm(request.POST, **form_kwargs)
if not form.is_valid(): if not form.is_valid():
return render(request, 'edit_session.html', return render(request, 'edit_session.html',
{'project': project, 'form': form}) {'project': project, 'form': form, 'session': None})
session = form.save(commit=False) session = form.save(commit=False)
session.user = request.user session.user = request.user
session.project = project session.project = project
@ -293,7 +304,8 @@ def create_or_edit_session(request, project_id, session_id=''):
else: else:
# Update an existing session with the form contents # Update an existing session with the form contents
session = Session.objects.get(pk=session_id) session = Session.objects.get(pk=session_id)
form = SessionForm(request.POST, instance=session) form_kwargs.update(instance=session)
form = SessionForm(request.POST, **form_kwargs)
if not form.is_valid(): if not form.is_valid():
return render(request, 'edit_session.html', return render(request, 'edit_session.html',
{'project': project, 'form': form, 'session': session}) {'project': project, 'form': form, 'session': session})
@ -308,17 +320,19 @@ def create_or_edit_session(request, project_id, session_id=''):
if session_id: if session_id:
# Return a pre-filled form for editing an existing session # Return a pre-filled form for editing an existing session
session = Session.objects.get(pk=session_id) session = Session.objects.get(pk=session_id)
form = SessionForm(instance=session) form_kwargs.update(instance=session)
form = SessionForm(**form_kwargs)
else: else:
# Return a new form with defaults for creating a new session # Return a new form with defaults for creating a new session
session = None session = None
form = SessionForm( form_kwargs.update(
initial={ initial={
'dbms': DBMSCatalog.objects.get( 'dbms': DBMSCatalog.objects.get(
type=DBMSType.POSTGRES, version='9.6'), type=DBMSType.POSTGRES, version='9.6'),
'algorithm': AlgorithmType.GPR, 'algorithm': AlgorithmType.GPR,
'target_objective': 'throughput_txn_per_sec', 'target_objective': 'throughput_txn_per_sec',
}) })
form = SessionForm(**form_kwargs)
context = { context = {
'project': project, 'project': project,
'session': session, 'session': session,