Skip to content

Commit 483960b

Browse files
coldfront: add tags
Signed-off-by: Cecilia Lau <cecilialau6776@gmail.com>
1 parent 146c9e6 commit 483960b

File tree

32 files changed

+1361
-6
lines changed

32 files changed

+1361
-6
lines changed

coldfront/config/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"coldfront.core.resource",
8484
"coldfront.core.allocation",
8585
"coldfront.core.grant",
86+
"coldfront.core.tag",
8687
"coldfront.core.publication",
8788
"coldfront.core.research_output",
8889
]

coldfront/config/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
path("project/", include("coldfront.core.project.urls")),
3333
path("allocation/", include("coldfront.core.allocation.urls")),
3434
path("resource/", include("coldfront.core.resource.urls")),
35+
path("tag/", include("coldfront.core.tag.urls")),
3536
]
3637

3738
if settings.GRANT_ENABLE:

coldfront/core/allocation/admin.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ class AllocationAttributeInline(admin.TabularInline):
5050
)
5151

5252

53+
class AllocationTagInline(admin.TabularInline):
54+
model = Allocation.tags.through
55+
extra = 0
56+
model._meta.verbose_name_plural = "Tags"
57+
58+
5359
class AllocationAdminNoteInline(admin.TabularInline):
5460
model = AllocationAdminNote
5561
extra = 0
@@ -99,8 +105,14 @@ class AllocationAdmin(SimpleHistoryAdmin):
99105
"created",
100106
"modified",
101107
)
102-
inlines = [AllocationUserInline, AllocationAttributeInline, AllocationAdminNoteInline, AllocationUserNoteInline]
103-
list_filter = ("resources__resource_type__name", "status", "resources__name", "is_locked")
108+
inlines = [
109+
AllocationUserInline,
110+
AllocationAttributeInline,
111+
AllocationTagInline,
112+
AllocationAdminNoteInline,
113+
AllocationUserNoteInline,
114+
]
115+
list_filter = ("resources__resource_type__name", "status", "resources__name", "is_locked", "tags")
104116
search_fields = [
105117
"project__pi__username",
106118
"project__pi__first_name",
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Generated by Django 4.2.23 on 2025-09-16 14:45
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("tag", "0001_initial"),
10+
("allocation", "0005_auto_20211117_1413"),
11+
]
12+
13+
operations = [
14+
migrations.AlterModelOptions(
15+
name="historicalallocation",
16+
options={
17+
"get_latest_by": ("history_date", "history_id"),
18+
"ordering": ("-history_date", "-history_id"),
19+
"verbose_name": "historical allocation",
20+
"verbose_name_plural": "historical allocations",
21+
},
22+
),
23+
migrations.AlterModelOptions(
24+
name="historicalallocationattribute",
25+
options={
26+
"get_latest_by": ("history_date", "history_id"),
27+
"ordering": ("-history_date", "-history_id"),
28+
"verbose_name": "historical allocation attribute",
29+
"verbose_name_plural": "historical allocation attributes",
30+
},
31+
),
32+
migrations.AlterModelOptions(
33+
name="historicalallocationattributechangerequest",
34+
options={
35+
"get_latest_by": ("history_date", "history_id"),
36+
"ordering": ("-history_date", "-history_id"),
37+
"verbose_name": "historical allocation attribute change request",
38+
"verbose_name_plural": "historical allocation attribute change requests",
39+
},
40+
),
41+
migrations.AlterModelOptions(
42+
name="historicalallocationattributetype",
43+
options={
44+
"get_latest_by": ("history_date", "history_id"),
45+
"ordering": ("-history_date", "-history_id"),
46+
"verbose_name": "historical allocation attribute type",
47+
"verbose_name_plural": "historical allocation attribute types",
48+
},
49+
),
50+
migrations.AlterModelOptions(
51+
name="historicalallocationattributeusage",
52+
options={
53+
"get_latest_by": ("history_date", "history_id"),
54+
"ordering": ("-history_date", "-history_id"),
55+
"verbose_name": "historical allocation attribute usage",
56+
"verbose_name_plural": "historical allocation attribute usages",
57+
},
58+
),
59+
migrations.AlterModelOptions(
60+
name="historicalallocationchangerequest",
61+
options={
62+
"get_latest_by": ("history_date", "history_id"),
63+
"ordering": ("-history_date", "-history_id"),
64+
"verbose_name": "historical allocation change request",
65+
"verbose_name_plural": "historical allocation change requests",
66+
},
67+
),
68+
migrations.AlterModelOptions(
69+
name="historicalallocationuser",
70+
options={
71+
"get_latest_by": ("history_date", "history_id"),
72+
"ordering": ("-history_date", "-history_id"),
73+
"verbose_name": "historical allocation user",
74+
"verbose_name_plural": "historical Allocation User Status",
75+
},
76+
),
77+
migrations.AddField(
78+
model_name="allocation",
79+
name="tags",
80+
field=models.ManyToManyField(blank=True, to="tag.tag"),
81+
),
82+
migrations.AlterField(
83+
model_name="historicalallocation",
84+
name="history_date",
85+
field=models.DateTimeField(db_index=True),
86+
),
87+
migrations.AlterField(
88+
model_name="historicalallocationattribute",
89+
name="history_date",
90+
field=models.DateTimeField(db_index=True),
91+
),
92+
migrations.AlterField(
93+
model_name="historicalallocationattributechangerequest",
94+
name="history_date",
95+
field=models.DateTimeField(db_index=True),
96+
),
97+
migrations.AlterField(
98+
model_name="historicalallocationattributetype",
99+
name="history_date",
100+
field=models.DateTimeField(db_index=True),
101+
),
102+
migrations.AlterField(
103+
model_name="historicalallocationattributeusage",
104+
name="history_date",
105+
field=models.DateTimeField(db_index=True),
106+
),
107+
migrations.AlterField(
108+
model_name="historicalallocationchangerequest",
109+
name="history_date",
110+
field=models.DateTimeField(db_index=True),
111+
),
112+
migrations.AlterField(
113+
model_name="historicalallocationuser",
114+
name="history_date",
115+
field=models.DateTimeField(db_index=True),
116+
),
117+
]

coldfront/core/allocation/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import coldfront.core.attribute_expansion as attribute_expansion
2020
from coldfront.core.project.models import Project, ProjectPermission
2121
from coldfront.core.resource.models import Resource
22+
from coldfront.core.tag.models import Tag
2223
from coldfront.core.utils.common import import_from_settings
2324

2425
logger = logging.getLogger(__name__)
@@ -67,6 +68,7 @@ class Allocation(TimeStampedModel):
6768
Attributes:
6869
project (Project): links the project the allocation falls under
6970
resources (Resource): links resources that this allocation allocates
71+
tags (Tag): links tags that apply to this allocation
7072
status (AllocationStatusChoice): represents the status of the allocation
7173
quantity (int): indicates the quantity of the resource for the allocation, if applicable
7274
start_date (Date): indicates the start date of the allocation
@@ -93,6 +95,7 @@ class Meta:
9395
on_delete=models.CASCADE,
9496
)
9597
resources = models.ManyToManyField(Resource)
98+
tags = models.ManyToManyField(Tag, blank=True)
9699
status = models.ForeignKey(AllocationStatusChoice, on_delete=models.CASCADE, verbose_name="Status")
97100
quantity = models.IntegerField(default=1)
98101
start_date = models.DateField(blank=True, null=True)

coldfront/core/allocation/templates/allocation/allocation_detail.html

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,65 @@ <h3 class="d-inline"><i class="fas fa-info-circle" aria-hidden="true"></i> EULA
208208
</div>
209209
{% endif %}
210210

211+
<div class="card mb-3">
212+
<div class="card-header">
213+
<h3 class="d-inline"><i class="fas fa-tags" aria-hidden="true"></i> Tags</h3>
214+
<div class="float-right">
215+
{% if may_edit_tags %}
216+
{% if request.user.is_superuser and tags %}
217+
<button id="tag_card_toggle_permission_icon" class="btn btn-info">
218+
<i class="fas fa-eye" aria-hidden="true"></i> Toggle Permission Icon
219+
</button>
220+
{% endif %}
221+
<a class="btn btn-primary" href="{% url 'allocation-tag-edit' allocation.pk %}?redirect_path={{ request.get_full_path|urlencode }}" role="button">
222+
<i class="fas fa-pen" aria-hidden="true"></i> Edit Tags
223+
</a>
224+
{% endif %}
225+
</div>
226+
</div>
227+
<div class="card-body">
228+
<div class="m-n1">
229+
{% spaceless %}
230+
{% for tag in tags %}
231+
<span class="d-inline-block rounded-pill font-weight-bold m-1 px-2 {{tag.get_html_classes}}">
232+
{% if request.user.is_superuser %}
233+
{% if tag.user_permissions == "none" %}
234+
<i class="fas fa-lock permission_icon" aria-hidden="true"></i>
235+
{% elif tag.user_permissions == "view" %}
236+
<i class="fas fa-eye permission_icon" aria-hidden="true"></i>
237+
{% elif tag.user_permissions == "edit" %}
238+
<i class="fas fa-pen permission_icon" aria-hidden="true"></i>
239+
{% endif %}
240+
{% endif %}
241+
{{tag}}</span>
242+
{% empty %}
243+
<div class="alert alert-info" role="alert"><i class="fas fa-info-circle" aria-hidden="true"></i> There are no tags to display.</div>
244+
{% endfor %}
245+
{% endspaceless %}
246+
</div>
247+
</div>
248+
</div>
249+
{% if request.user.is_superuser %}
250+
<script>
251+
const eye_class = "fa-eye";
252+
const eye_slash_class = "fa-eye-slash";
253+
$(document).ready(function() {
254+
const toggle_button = $("#tag_card_toggle_permission_icon")
255+
toggle_button.on("click", () => {
256+
if (toggle_button.children("i").hasClass(eye_class)) {
257+
toggle_button.children("i").removeClass(eye_class);
258+
toggle_button.children("i").addClass(eye_slash_class);
259+
$(".permission_icon").addClass("d-none");
260+
} else {
261+
toggle_button.children("i").removeClass(eye_slash_class);
262+
toggle_button.children("i").addClass(eye_class);
263+
$(".permission_icon").removeClass("d-none");
264+
}
265+
})
266+
})
267+
</script>
268+
{% endif %}
269+
211270
{% if attributes or attributes_with_usage or request.user.is_superuser %}
212271
<div class="card mb-3">
213272
<div class="card-header">
@@ -477,4 +536,4 @@ <h3 class="d-inline"><i class="fas fa-users" aria-hidden="true"></i> Notificatio
477536
}
478537
})
479538
</script>
480-
{% endblock %}
539+
{% endblock %}

coldfront/core/allocation/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
path(
7171
"allocation-account-list/", allocation_views.AllocationAccountListView.as_view(), name="allocation-account-list"
7272
),
73+
path("<int:pk>/allocationtag/edit", allocation_views.AllocationTagEditView.as_view(), name="allocation-tag-edit"),
7374
]
7475

7576
if ALLOCATION_EULA_ENABLE:

coldfront/core/allocation/views.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@
6969
from coldfront.core.allocation.utils import generate_guauge_data_from_usage, get_user_resources
7070
from coldfront.core.project.models import Project, ProjectPermission, ProjectUser, ProjectUserStatusChoice
7171
from coldfront.core.resource.models import Resource
72+
from coldfront.core.tag.models import Tag
73+
from coldfront.core.tag.views import TagsEditView
7274
from coldfront.core.utils.common import get_domain_url, import_from_settings
7375
from coldfront.core.utils.mail import (
7476
build_link,
@@ -185,6 +187,11 @@ def get_context_data(self, **kwargs):
185187

186188
context["notes"] = notes
187189
context["ALLOCATION_ENABLE_ALLOCATION_RENEWAL"] = ALLOCATION_ENABLE_ALLOCATION_RENEWAL
190+
191+
tags = allocation_obj.tags
192+
193+
context["tags"] = Tag.get_tags_visible_to_user(tags, self.request.user)
194+
context["may_edit_tags"] = allocation_obj.has_perm(self.request.user, AllocationPermission.MANAGER)
188195
return context
189196

190197
def get(self, request, *args, **kwargs):
@@ -331,6 +338,19 @@ def post(self, request, *args, **kwargs):
331338
return HttpResponseRedirect(reverse("allocation-detail", kwargs={"pk": pk}))
332339

333340

341+
class AllocationTagEditView(TagsEditView):
342+
model = Allocation
343+
344+
def test_func(self):
345+
"""UserPassesTestMixin Tests"""
346+
allocation_obj = get_object_or_404(self.model, pk=self.kwargs.get("pk"))
347+
if allocation_obj.has_perm(self.request.user, AllocationPermission.MANAGER):
348+
return True
349+
350+
messages.error(self.request, "You do not have permission to edit tags.")
351+
return False
352+
353+
334354
class AllocationEULAView(LoginRequiredMixin, UserPassesTestMixin, TemplateView):
335355
model = Allocation
336356
template_name = "allocation/allocation_review_eula.html"

coldfront/core/project/admin.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ class ProjectAttributeInLine(admin.TabularInline):
142142
)
143143

144144

145+
class ProjectTagInline(admin.TabularInline):
146+
model = Project.tags.through
147+
extra = 0
148+
model._meta.verbose_name_plural = "Tags"
149+
150+
145151
@admin.register(AttributeType)
146152
class AttributeTypeAdmin(admin.ModelAdmin):
147153
list_display = ("name",)
@@ -334,8 +340,14 @@ class ProjectAdmin(SimpleHistoryAdmin):
334340
"projectuser__user__last_name",
335341
"title",
336342
]
337-
list_filter = ("status", "force_review")
338-
inlines = [ProjectUserInline, ProjectAdminCommentInline, ProjectUserMessageInline, ProjectAttributeInLine]
343+
list_filter = ("status", "force_review", "tags")
344+
inlines = [
345+
ProjectUserInline,
346+
ProjectAdminCommentInline,
347+
ProjectUserMessageInline,
348+
ProjectAttributeInLine,
349+
ProjectTagInline,
350+
]
339351
raw_id_fields = [
340352
"pi",
341353
]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Generated by Django 4.2.23 on 2025-09-16 14:45
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("tag", "0001_initial"),
10+
("project", "0006_historicalproject_institution_project_institution"),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name="project",
16+
name="tags",
17+
field=models.ManyToManyField(blank=True, to="tag.tag"),
18+
),
19+
]

0 commit comments

Comments
 (0)