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

Fixed bugs with assignments, added status info to detail and list view #12

Closed
wants to merge 17 commits into from
Closed
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
1 change: 0 additions & 1 deletion demo/article/admin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from django.contrib import admin

from signoffs.contrib.signets.models import RevokedSignet, Signet

from .models import Article, ArticleSignet, Comment, LikeSignet

# Signoffs Models
Expand Down
9 changes: 3 additions & 6 deletions demo/article/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@

from signoffs.models import Signet, SignoffSet, SignoffSingle
from signoffs.signoffs import SignoffRenderer, SignoffUrlsManager, SimpleSignoff

from ..signoffs import publication_approval_signoff, publication_request_signoff
from .signets import LikeSignet
from ..signoffs import publication_approval_signoff, publication_request_signoff


class Article(models.Model):
Expand Down Expand Up @@ -36,6 +35,7 @@ class PublicationStatus(models.TextChoices):
"like_signoff",
signet_set_accessor="like_signatories",
)
total_likes = models.IntegerField()

def update_publication_status(self):
status = self.PublicationStatus.NOT_REQUESTED
Expand All @@ -57,14 +57,11 @@ def __str__(self):
else:
return f"{self.author.username} - {self.title}"

def delete(self, *args, **kwargs):
def delete(self, *args, **kwargs): # FIXME: no longer needed?
# if self.is_published:
# self.publish_signet.delete() # Delete the signet associated with the article
super().delete(*args, **kwargs) # Delete the article itself

def total_likes(self):
return self.likes.count()

def is_author(self, user=None, username=None):
if user is None and username is None:
raise ValueError("Either user or username must be provided.")
Expand Down
1 change: 1 addition & 0 deletions demo/article/models/signets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from signoffs.models import Signet


# from article.models.models import Article

#
Expand Down
4 changes: 2 additions & 2 deletions demo/article/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
from django.urls import reverse

from signoffs.shortcuts import get_signet_or_404

from ..registration import permissions
from .forms import ArticleForm, CommentForm
from .models.models import Article, Comment, comment_signoff
from .models.signets import ArticleSignet, LikeSignet
from .signoffs import publication_approval_signoff, publication_request_signoff
from ..registration import permissions


# Article CRUD views

Expand Down
1 change: 0 additions & 1 deletion demo/assignments/admin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from django.contrib import admin

from signoffs.models import Stamp

from .models import Assignment

admin.site.register(Assignment)
Expand Down
16 changes: 16 additions & 0 deletions demo/assignments/approvals.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from django.contrib.auth.models import User
from django.utils.functional import SimpleLazyObject

from signoffs.approvals import ApprovalSignoff, SimpleApproval
from signoffs.models import ApprovalSignet
from signoffs.registry import register
Expand Down Expand Up @@ -41,3 +44,16 @@ class NewAssignmentApproval(SimpleApproval):
submit_completed_signoff, # submitted
confirm_completion_signoff, # completed - unrevokable?
)

def next_signoffs(self, for_user=None):
if not for_user:
return super().next_signoffs()
if not type(for_user) in (User, SimpleLazyObject):
raise TypeError(f"var \"for_user\" must be User instance, instead got {type(for_user)}\n")

assignment = self.subject
if (for_user == assignment.assigned_by and assignment.status in ['draft', 'pending_review']) or (for_user == assignment.assigned_to and assignment.status in ['requested', 'in_progress']):
return super().next_signoffs(for_user=for_user)
else:
return []

6 changes: 3 additions & 3 deletions demo/assignments/forms.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.forms import HiddenInput, ModelForm, Textarea, TextInput
from django.forms import ModelForm, Textarea, TextInput

from .models import Assignment

Expand All @@ -7,9 +7,9 @@ class AssignmentForm(ModelForm):
class Meta:
model = Assignment
fields = ["assignment_name", "assigned_to", "details"]
hidden = ["assigned_by"]
# hidden = ["assigned_by"]
widgets = {
"assignment_name": TextInput(attrs={"style": "width:75%"}),
"details": Textarea(attrs={"rows": 4, "style": "width:100%"}),
"assigned_by": HiddenInput(),
# "assigned_by": HiddenInput(),
}
26 changes: 26 additions & 0 deletions demo/assignments/migrations/0002_auto_20240508_1703.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Django 3.2.20 on 2024-05-08 17:03

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


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('assignments', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='assignment',
name='assigned_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assigned_by', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='assignment',
name='assigned_to',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assigned_to', to=settings.AUTH_USER_MODEL),
),
]
18 changes: 18 additions & 0 deletions demo/assignments/migrations/0003_assignment_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.20 on 2024-05-09 17:39

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('assignments', '0002_auto_20240508_1703'),
]

operations = [
migrations.AddField(
model_name='assignment',
name='status',
field=models.TextField(choices=[('draft', 'Draft'), ('requested', 'Requested'), ('in_progress', 'In Progress'), ('pending_review', 'Pending Review'), ('completed', 'Completed')], default=('draft', 'Draft'), max_length=15),
),
]
27 changes: 23 additions & 4 deletions demo/assignments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,37 @@
from django.db import models

from signoffs.models import ApprovalField

from .approvals import NewAssignmentApproval


class Assignment(models.Model):
STATUS_OPTS = (
("draft", "Draft"),
("requested", "Requested"),
("in_progress", "In Progress"),
("pending_review", "Pending Review"),
("completed", "Completed"),
)
assignment_name = models.CharField(max_length=200)
assigned_to = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
assigned_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name="created_assignment")
assigned_to = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name="received_assignment")
status = models.CharField(max_length=15, null=False, default=STATUS_OPTS[0][0], choices=STATUS_OPTS)
details = models.TextField(max_length=1000)
approval, approval_stamp = ApprovalField(NewAssignmentApproval)

def assignee(self):
if self.assigned_to.get_full_name():
return self.assigned_to.get_full_name()
name = self.assigned_to.get_full_name()
if name:
return name
else:
return self.assigned_to.username

def bump_status(self, commit=True):
current_index = self.STATUS_OPTS.index([status for status in self.STATUS_OPTS if status[0] == self.status][0]) #TODO: implement cleaner way of getting current index
num_opts = len(self.STATUS_OPTS)
if num_opts <= current_index + 1:
self.status = self.STATUS_OPTS[num_opts - 1][0]
else:
self.status = self.STATUS_OPTS[current_index + 1][0]
if commit:
self.save()
10 changes: 7 additions & 3 deletions demo/assignments/templates/assignments/assignment_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,21 @@ <h6>Something went wrong...</h6>
</div>
{% endif %}
<h1>{{ assignment.assignment_name }}</h1>
<h6 class="text-success">{{ assignment.get_status_display }}</h6>
<hr>
<p>
<strong>Assigned To:</strong>
<strong>Assigned By:</strong> {{ assignment.assigned_by.get_full_name }} <text class="text-muted">{{ assignment.assigned_by }}</text>
<br>
{{ assignment.assignee }}
<strong>Assigned To:</strong> {{ assignment.assigned_to.get_full_name }} <text class="text-muted">{{ assignment.assigned_to }}</text>
</p>
<p>
<strong>Assignment Details:</strong>
<br>{{ assignment.details }}
<br>
{{ assignment.details }}
<br>
</p>
</div>
{#{% if signoff %}#}
<br>
<form method="post">
<div class="signoffs approval-signoff">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ <h1>{{ page_title}} <small>({{ assignments|length }})</small></h1>
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">{{ assignment.assignment_name }}</h5>
<h6 class="text-success">{{ assignment.get_status_display }}</h6>
<p class="card-text">Assigned To: {{ assignment.assignee }}</p>
<a href="{% url 'assignment:detail' assignment_id=assignment.id %}" class="btn btn-outline-dark">See Assignment Details</a>
</div>
Expand Down
11 changes: 9 additions & 2 deletions demo/assignments/templates/assignments/create_assignment.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@
<div class="col-lg-6">
<div class="card mb-5">
<div class="card-header bg-dark text-white">
<h3 class="card-title mt-3">Initialize a New Assignment</h3>
<h3 class="card-title mt-3">Create a New Assignment</h3>
{% if messages %}
<div class="messages text-center">
{% for message in messages %}
<p><em>{{ message }}</em></p>
{% endfor %}
</div>
{% endif %}
</div>
<div class="card-body">
{% if messages %}
Expand Down Expand Up @@ -43,7 +50,7 @@ <h6>Something went wrong...</h6>
</div>
</div>
{# {{ signoff_form.as_p }}#}
<button class="btn btn-success" type="submit">Submit</button>
<button class="btn btn-success" type="submit">Save</button>
{# <div class="signoffs approval-signoff">#}
{# {% render_signoff assignment_approval %}#}
{# </div>#}
Expand Down
7 changes: 7 additions & 0 deletions demo/assignments/templates/assignments/new_assignment.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
<div>
<form name="project-detail" method="POST" action="{% url "assignment:detail" %}">
{% csrf_token %}
{% if messages %}
<div class="messages text-center">
{% for message in messages %}
<p><em>{{ message }}</em></p>
{% endfor %}
</div>
{% endif %}
{{ project_form.as_p }}
<div class="signoffs approval-panels">
{% render_approval approval_form show_revoke=True %}
Expand Down
45 changes: 27 additions & 18 deletions demo/assignments/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,30 @@
CRUD and list views for Assignment app
"""
from django.contrib import messages
from django.contrib.auth.decorators import login_required, user_passes_test, permission_required
from django.shortcuts import HttpResponseRedirect, get_object_or_404, render, reverse

from .forms import AssignmentForm
from .models import Assignment
from ..registration import permissions


@login_required
@user_passes_test(permissions.has_signed_terms, login_url="terms_of_service")
@permission_required("is_staff", login_url="my_assignments", raise_exception="Only staff users may create assignments")
def create_assignment_view(request):
if not request.user.is_staff:
messages.error(
request, "You must be registered as staff to create a new assignment."
)
form = AssignmentForm

if request.method == "POST":
if request.method == "POST" and request.user.is_staff:
form = form(request.POST)
if form.is_valid():
assignment = form.save()
assignment = form.save(commit=False)
assignment.assigned_by = request.user
assignment.save()
return HttpResponseRedirect(
reverse("assignment:detail", args=(assignment.id,))
)
Expand All @@ -32,36 +39,36 @@ def create_assignment_view(request):
)


@login_required
@user_passes_test(permissions.has_signed_terms, login_url="terms_of_service")
def assignment_detail_view(request, assignment_id):
if not request.user.is_staff:
messages.error(
request, "You must be registered as staff to create a new project."
)

assignment = get_object_or_404(Assignment, pk=assignment_id)
if request.method == "POST":
return assignment_signoffs_view(request, assignment_id)
# signoff = assignment.approval.get_next_signoff(for_user=request.user)
if request.method == "POST": # and signoff:
return sign_assignment_view(request, assignment_id)
else:
context = {"assignment": assignment}
context = {"assignment": assignment}#, "signoff": signoff}
return render(request, "assignments/assignment_detail.html", context=context)


def assignment_signoffs_view(request, assignment_id):
@login_required
@user_passes_test(permissions.has_signed_terms, login_url="terms_of_service")
def sign_assignment_view(request, assignment_id):
assignment = get_object_or_404(Assignment, pk=assignment_id)
if request.method == "POST":
if request.user == assignment.assigned_to or request.user.is_staff:
signoff = assignment.approval.get_next_signoff(for_user=request.user)
signoff_form = signoff.forms.get_signoff_form(request.POST)
if signoff_form.is_valid():
signoff_form.sign(request.user)
signoff = assignment.approval.get_next_signoff(for_user=request.user)
if request.method == "POST" and signoff:
signoff_form = signoff.forms.get_signoff_form(request.POST)
if signoff_form.is_valid():
signoff.sign(request.user, commit=True)
assignment.bump_status()
assignment.save()
else:
messages.error(request, "You do not have permission to sign this signoff")
return HttpResponseRedirect(reverse("assignment:detail", args=(assignment.id,)))


# List views


def my_assignments_view(request):
page_title = "My Assignments"
empty_text = "You have no assignments"
Expand All @@ -76,6 +83,8 @@ def all_assignments_view(request):
return assignment_list_base_view(request, page_title, empty_text)


@login_required
@user_passes_test(permissions.has_signed_terms, login_url="terms_of_service")
def assignment_list_base_view(
request, page_title=None, empty_text=None, **filter_kwargs
):
Expand Down
Loading
Loading