From c568c09c000edc4e2c0d35bde97bc016126fccb5 Mon Sep 17 00:00:00 2001 From: Dana Van Aken Date: Tue, 8 Oct 2019 05:03:11 -0400 Subject: [PATCH] Enforce unique Project.name for same user and unique Session.name for same user and project --- server/website/website/forms.py | 42 +++++++++++++++++-- .../website/migrations/0001_initial.py | 18 ++++---- server/website/website/models.py | 15 +++++-- .../website/templates/project_sessions.html | 8 ++-- server/website/website/templates/session.html | 10 ++--- server/website/website/views.py | 40 ++++++++++++------ 6 files changed, 96 insertions(+), 37 deletions(-) diff --git a/server/website/website/forms.py b/server/website/website/forms.py index 75ff36c..c463a2e 100644 --- a/server/website/website/forms.py +++ b/server/website/website/forms.py @@ -8,7 +8,6 @@ Created on Jul 25, 2017 @author: dvanaken ''' - from django import forms from .models import Session, Project, Hardware, SessionKnob @@ -25,6 +24,24 @@ class NewResultForm(forms.Form): 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 model = Project @@ -50,7 +67,11 @@ class SessionForm(forms.ModelForm): storage_type = forms.ChoiceField(label='Storage Type', choices=StorageType.choices()) 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['target_objective'].required = False self.fields['tuning_session'].required = True @@ -59,8 +80,21 @@ class SessionForm(forms.ModelForm): self.fields['storage'].initial = 32 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): - model = super(SessionForm, self).save(commit=False) + model = super().save(commit=False) cpu2 = self.cleaned_data['cpu'] memory2 = self.cleaned_data['memory'] @@ -99,7 +133,7 @@ class SessionKnobForm(forms.ModelForm): name = forms.CharField(max_length=128) def __init__(self, *args, **kwargs): - super(SessionKnobForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['session'].required = False self.fields['knob'].required = False self.fields['name'].widget.attrs['readonly'] = True diff --git a/server/website/website/migrations/0001_initial.py b/server/website/website/migrations/0001_initial.py index 76036a1..67d2d34 100644 --- a/server/website/website/migrations/0001_initial.py +++ b/server/website/website/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- 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 django.conf import settings @@ -150,9 +150,6 @@ class Migration(migrations.Migration): ('last_update', models.DateTimeField()), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], - options={ - 'abstract': False, - }, ), migrations.CreateModel( name='Result', @@ -186,16 +183,13 @@ class Migration(migrations.Migration): ('creation_time', models.DateTimeField()), ('last_update', models.DateTimeField()), ('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)), ('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')), ('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)), ], - options={ - 'abstract': False, - }, ), migrations.CreateModel( name='SessionKnob', @@ -264,6 +258,14 @@ class Migration(migrations.Migration): name='workload', 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( name='pipelinedata', unique_together=set([('pipeline_run', 'task_type', 'workload')]), diff --git a/server/website/website/models.py b/server/website/website/models.py index aa02e1b..647e22e 100644 --- a/server/website/website/models.py +++ b/server/website/website/models.py @@ -164,6 +164,9 @@ class Project(BaseModel): x.delete() super(Project, self).delete(using, keep_parents) + class Meta: # pylint: disable=old-style-class,no-init + unique_together = ('user', 'name') + class Hardware(BaseModel): @@ -201,13 +204,14 @@ class Session(BaseModel): last_update = models.DateTimeField() upload_code = models.CharField(max_length=30, unique=True) - TUNING_OPTIONS = [ + TUNING_OPTIONS = OrderedDict([ ("tuning_session", "Tuning Session"), ("no_tuning_session", "No Tuning"), ("randomly_generate", "Randomly Generate") - ] - tuning_session = models.CharField(choices=TUNING_OPTIONS, - max_length=64, default='tuning_session') + ]) + tuning_session = models.CharField(choices=TUNING_OPTIONS.items(), + max_length=64, default='tuning_session', + verbose_name="session type") TARGET_OBJECTIVES = [ ('throughput_txn_per_sec', 'Throughput'), @@ -228,6 +232,9 @@ class Session(BaseModel): r.delete() 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): @staticmethod diff --git a/server/website/website/templates/project_sessions.html b/server/website/website/templates/project_sessions.html index 00fa131..44e3785 100644 --- a/server/website/website/templates/project_sessions.html +++ b/server/website/website/templates/project_sessions.html @@ -13,7 +13,8 @@ {{ labels.dbms }} {{ labels.hardware }} {{ labels.tuning_session }} - {{ labels.creation_time }} + {{ labels.algorithm }} + {{ labels.last_update }} {% for session in sessions %} @@ -22,8 +23,9 @@ {{ session.name }} {{ session.dbms.full_name }} {{ session.hardware }} - {{ session.tuning_session }} - {{ session.creation_time }} + {{ session.session_type_name }} + {{ session.algorithm_name }} + {{ session.last_update }} {% endfor %} diff --git a/server/website/website/templates/session.html b/server/website/website/templates/session.html index 32dc6fd..d489e43 100644 --- a/server/website/website/templates/session.html +++ b/server/website/website/templates/session.html @@ -89,9 +89,13 @@ caption span {float: right;}
{{ labels.hardware }}
{{ session.hardware }} + +
{{ labels.tuning_session }}
+ {{ session.session_type_name }} +
{{ labels.algorithm }}
- {{ algorithm_name }} + {{ session.algorithm_name }}
{{ labels.creation_time }}
@@ -101,10 +105,6 @@ caption span {float: right;}
{{ labels.last_update }}
{{ session.last_update }} - -
{{ labels.tuning_session }}
- {{ session.tuning_session }} -
{{ labels.target_objective }}
{{ metric_meta|get_item:session.target_objective|get_attr:"pprint" }} diff --git a/server/website/website/views.py b/server/website/website/views.py index 83b2f70..7a62f8e 100644 --- a/server/website/website/views.py +++ b/server/website/website/views.py @@ -141,9 +141,10 @@ def home_projects_view(request): @login_required(login_url=reverse_lazy('login')) 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 project_id == '': - form = ProjectForm(request.POST) + form = ProjectForm(request.POST, **form_kwargs) if not form.is_valid(): return render(request, 'edit_project.html', {'form': form}) project = form.save(commit=False) @@ -151,22 +152,24 @@ def create_or_edit_project(request, project_id=''): ts = now() project.creation_time = ts project.last_update = ts - project.save() else: 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(): return render(request, 'edit_project.html', {'form': form}) project.last_update = now() - project.save() + + project.save() return redirect(reverse('project_sessions', kwargs={'project_id': project.pk})) else: if project_id == '': project = None - form = ProjectForm() + form = ProjectForm(**form_kwargs) else: - project = Project.objects.get(pk=int(project_id)) - form = ProjectForm(instance=project) + project = Project.objects.get(pk=project_id) + form_kwargs.update(instance=project) + form = ProjectForm(**form_kwargs) context = { 'project': project, 'form': form, @@ -191,6 +194,10 @@ def project_sessions_view(request, project_id): 'button_create': 'create a new session', })) 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 = { "sessions": sessions, "project": project, @@ -245,8 +252,12 @@ def session_view(request, project_id, session_id): knobs = SessionKnob.objects.get_knobs_for_session(session) 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['title'] = "Session Info" + context = { 'project': project, 'dbmss': dbmss, @@ -263,7 +274,6 @@ def session_view(request, project_id, session_id): 'knob_names': knob_names, 'filters': [], 'session': session, - 'algorithm_name': AlgorithmType.TYPE_NAMES[session.algorithm], 'results': results, 'labels': form_labels, } @@ -274,13 +284,14 @@ def session_view(request, project_id, session_id): @login_required(login_url=reverse_lazy('login')) def create_or_edit_session(request, project_id, session_id=''): 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 not session_id: # Create a new session from the form contents - form = SessionForm(request.POST) + form = SessionForm(request.POST, **form_kwargs) if not form.is_valid(): return render(request, 'edit_session.html', - {'project': project, 'form': form}) + {'project': project, 'form': form, 'session': None}) session = form.save(commit=False) session.user = request.user session.project = project @@ -293,7 +304,8 @@ def create_or_edit_session(request, project_id, session_id=''): else: # Update an existing session with the form contents 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(): return render(request, 'edit_session.html', {'project': project, 'form': form, 'session': session}) @@ -308,17 +320,19 @@ def create_or_edit_session(request, project_id, session_id=''): if session_id: # Return a pre-filled form for editing an existing session session = Session.objects.get(pk=session_id) - form = SessionForm(instance=session) + form_kwargs.update(instance=session) + form = SessionForm(**form_kwargs) else: # Return a new form with defaults for creating a new session session = None - form = SessionForm( + form_kwargs.update( initial={ 'dbms': DBMSCatalog.objects.get( type=DBMSType.POSTGRES, version='9.6'), 'algorithm': AlgorithmType.GPR, 'target_objective': 'throughput_txn_per_sec', }) + form = SessionForm(**form_kwargs) context = { 'project': project, 'session': session,