Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] Proposal reviewing mechanisms #34

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions custom_auth/migrations/0002_user_is_reviewer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.13 on 2024-12-10 16:46

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('custom_auth', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='user',
name='is_reviewer',
field=models.BooleanField(default=False, verbose_name='is reviewer'),
),
]
2 changes: 2 additions & 0 deletions custom_auth/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ class User(AbstractBaseUser, PermissionsMixin):
is_staff = models.BooleanField("is staff", default=False)
is_superuser = models.BooleanField("is superuser", default=False)

is_reviewer = models.BooleanField("is reviewer", default=False)

USERNAME_FIELD = "email"
REQUIRED_FIELDS = []

Expand Down
87 changes: 87 additions & 0 deletions proposals/management/commands/create_demo_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from django.core.management.base import BaseCommand
from django.contrib.auth import get_user_model
from proposals.models import ProposalType, Track, Proposal, ReviewAspect
import random

User = get_user_model()


class Command(BaseCommand):
help = "Create a bunch of fake data to play with"

def handle(self, *args, **options):
super, _ = User.objects.get_or_create(
is_staff=True, is_superuser=True, email="[email protected]"
)
super.set_password("[email protected]")
super.save()

p1, _ = ProposalType.objects.get_or_create(
name="long talk",
defaults={
"max_duration_minutes": 40,
"description": "A lot of talking. Really quite a lot. Best for talkative folks who like speaking. A lot",
},
)

p2, _ = ProposalType.objects.get_or_create(
name="short talk",
defaults={
"max_duration_minutes": 20,
"description": "Not so much talking",
},
)

p3, _ = ProposalType.objects.get_or_create(
name="poster",
defaults={
"description": "No talking",
},
)

proposal_types = [p1, p2, p3]

t1, _ = Track.objects.get_or_create(name="Community")
t2, _ = Track.objects.get_or_create(name="Ops")
t3, _ = Track.objects.get_or_create(name="Frontend")
t4, _ = Track.objects.get_or_create(name="Security")

tracks = [t1, t2, t3, t4]

ReviewAspect.objects.get_or_create(
name="Meaningful title",
data_type=ReviewAspect.DATA_TYPE_SCORE,
defaults={
"help_text": "0 = terrible\n 1 = kinda bad\n 2= ok \n 3=pretty good \n 4 =poetry"
},
)
ReviewAspect.objects.get_or_create(
name="Blatant use of LLMs",
data_type=ReviewAspect.DATA_TYPE_BOOLEAN,
defaults={"help_text": "Does it look like this was written by a robot?"},
)

def create_user_and_proposals(email, is_reviewer):
user, _ = User.objects.get_or_create(
email=email, defaults={"is_reviewer": is_reviewer}
)
user.set_password(email)
user.save()

for n in range(3):
Proposal.objects.get_or_create(
title=f"Title for {email} proposal {n}",
user=user,
defaults={
"track": random.choice(tracks),
"proposal_type": random.choice(proposal_types),
"description": "The quick red fox jumps over the lazy dog" * 10,
"audience_level": random.choice(
Proposal.AUDIENCE_LEVEL_CHOICES
)[0],
},
)

create_user_and_proposals(email="[email protected]", is_reviewer=False)
create_user_and_proposals(email="[email protected]", is_reviewer=True)
create_user_and_proposals(email="[email protected]", is_reviewer=True)
45 changes: 45 additions & 0 deletions proposals/migrations/0002_review_reviewaspect_reviewaspectscore.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Generated by Django 4.2.13 on 2024-12-10 16:46

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),
('proposals', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='Review',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('notes', models.TextField()),
('hard_no', models.BooleanField(default=False)),
('favourite', models.BooleanField(default=False)),
('reviewer_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='ReviewAspect',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField()),
('data_type', models.CharField(choices=[('true/false', 'true/false'), ('score', 'score')])),
('minimum_score', models.SmallIntegerField(default=0)),
('maximum_score', models.SmallIntegerField(default=5)),
('help_text', models.TextField()),
],
),
migrations.CreateModel(
name='ReviewAspectScore',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('aspect', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='proposals.reviewaspect')),
('review', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='proposals.review')),
],
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 4.2.13 on 2024-12-10 18:11

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('proposals', '0002_review_reviewaspect_reviewaspectscore'),
]

operations = [
migrations.AddField(
model_name='reviewaspectscore',
name='bool',
field=models.BooleanField(blank=True, null=True),
),
migrations.AddField(
model_name='reviewaspectscore',
name='score',
field=models.SmallIntegerField(blank=True, null=True),
),
migrations.AlterField(
model_name='reviewaspect',
name='maximum_score',
field=models.SmallIntegerField(default=4),
),
]
38 changes: 38 additions & 0 deletions proposals/models.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 8, you forgot the rest two " on the docstring 🙂

Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,41 @@ class Proposal(models.Model):

def __str__(self):
return f"{self.title}"


class ReviewAspect(models.Model):
name = models.CharField()
# slug = TODO

DATA_TYPE_SCORE = "score"
DATA_TYPE_BOOLEAN = "true/false"

DATA_TYPE_CHOICES = [
(DATA_TYPE_BOOLEAN, DATA_TYPE_BOOLEAN),
(DATA_TYPE_SCORE, DATA_TYPE_SCORE),
]
data_type = models.CharField(choices=DATA_TYPE_CHOICES)

# if we are using a SCORE datatype then these are used
minimum_score = models.SmallIntegerField(default=0)
maximum_score = models.SmallIntegerField(default=4)

help_text = (
models.TextField()
) # use this to describe the aspect. Eg 0=terrible, 5=awesome


class Review(models.Model):
reviewer_user = models.ForeignKey(User, on_delete=models.CASCADE)
notes = models.TextField()

hard_no = models.BooleanField(default=False)
favourite = models.BooleanField(default=False)


class ReviewAspectScore(models.Model):
aspect = models.ForeignKey(ReviewAspect, on_delete=models.PROTECT)
review = models.ForeignKey(Review, on_delete=models.CASCADE)

score = models.SmallIntegerField(null=True, blank=True)
bool = models.BooleanField(null=True, blank=True)
6 changes: 6 additions & 0 deletions proposals/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@
views.edit_my_proposal,
name="edit_my_proposal",
),
path("reviewer_dashboard", views.reviewer_dashboard, name="reviewer_dashboard"),
path(
"proposals/<int:proposal_id>/review",
views.add_edit_review,
name="add_edit_review",
),
]
67 changes: 67 additions & 0 deletions proposals/views.py
Copy link
Collaborator

@theShinigami theShinigami Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When creating a new proposal on create_proposal view, the forms module is getting overridden by django's forms it's not using .forms 🙂

image

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.contrib.auth.decorators import user_passes_test

from . import models
from . import forms
Expand Down Expand Up @@ -63,3 +64,69 @@

context = {"form": form}
return render(request, "proposals/edit_proposal.html", context)


@user_passes_test(lambda user: user.is_reviewer)
def reviewer_dashboard(request):

proposals = models.Proposal.objects.all() # TODO: filter appropriately
context = {"proposals": proposals}
return render(request, "proposals/reviewer_dashboard.html", context)


from django import forms

Check failure on line 77 in proposals/views.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E402)

proposals/views.py:77:1: E402 Module level import not at top of file

Check failure on line 77 in proposals/views.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (F811)

proposals/views.py:77:20: F811 Redefinition of unused `forms` from line 9


def aspect_to_form_field(aspect: models.ReviewAspect):
if aspect.data_type == aspect.DATA_TYPE_BOOLEAN:
return forms.NullBooleanField(
widget=forms.Select(
choices=[
("", "Unknown"),
(True, "Yes"),
(False, "No"),
]
),
help_text=aspect.help_text,
)
if aspect.data_type == aspect.DATA_TYPE_SCORE:
return forms.IntegerField(
max_value=aspect.maximum_score,
min_value=aspect.minimum_score,
help_text=aspect.help_text,
)


def create_review_form_class():

properties = {
aspect.name: aspect_to_form_field(aspect)
for aspect in models.ReviewAspect.objects.all()
}

class ReviewFormMeta(forms.models.ModelFormMetaclass):
model = models.Review

fields = ["notes", "hard_no", "favourite"]
help_texts = {
"hard_no": "Tick this box if you are strongly against accepting this talk",
"favourite": "Tick this box if you are really keen to see this talk",
}

properties["Meta"] = ReviewFormMeta

ReviewFrom = type("ReviewFrom", (forms.ModelForm,), properties)

return ReviewFrom


@user_passes_test(lambda user: user.is_reviewer)
def add_edit_review(request, proposal_id):
# TODO: Cant reviwe own proposal
proposal = get_object_or_404(models.Proposal, pk=proposal_id)
ReviewFrom = create_review_form_class()

form = ReviewFrom()
context = {"form": form, "proposal": proposal}

return render(request, "proposals/add_edit_review.html", context=context)
16 changes: 16 additions & 0 deletions static/src/tailwind_final.css
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,14 @@ td {
max-width: 80rem;
}

.max-w-4xl {
max-width: 56rem;
}

.max-w-2xl {
max-width: 42rem;
}

.table-auto {
table-layout: auto;
}
Expand All @@ -941,6 +949,10 @@ td {
appearance: none;
}

.grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}

.flex-row {
flex-direction: row;
}
Expand Down Expand Up @@ -1100,6 +1112,10 @@ td {
padding: 1.25rem;
}

.p-2 {
padding: 0.5rem;
}

.px-10 {
padding-left: 2.5rem;
padding-right: 2.5rem;
Expand Down
8 changes: 3 additions & 5 deletions templates/_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,18 @@
{% partial header-menu-item-large %}
{% endfor %}

<!--


{% if user.is_authenticated %}
{% with link=user_loggedin_link %}
{% partial header-menu-item-large %}
{% endwith %}
{% else %}

{% with link=user_not_loggedin_link %}
{% partial header-menu-item-large %}
{% endwith %}

{% endif %}
-->

</div>
</div>
Expand Down
Loading
Loading