Skip to content

Commit 5932fe1

Browse files
authored
Merge pull request #602 from 508312/master
Bitbyte model. Bitbyte tree fix.
2 parents afdbd3d + 4ce9b51 commit 5932fe1

14 files changed

Lines changed: 280 additions & 1064 deletions

hknweb/candidate/admin/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from hknweb.candidate.admin.announcement import AnnouncementAdmin
22
from hknweb.candidate.admin.bitbyteactivity import BitByteActivityAdmin
3+
from hknweb.candidate.admin.bit_byte_group import BitByteGroupAdmin
34
from hknweb.candidate.admin.officer_challenge import OffChallengeAdmin
45
from hknweb.candidate.admin.logistics import LogisticsAdmin
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
from django.contrib import admin, messages
2+
from django.shortcuts import render, redirect
3+
from django.urls import path
4+
from django.db import transaction
5+
from django.contrib.auth.models import User
6+
7+
import io
8+
import csv
9+
from hknweb.coursesemester.models import Semester
10+
from hknweb.candidate.models import BitByteGroup
11+
from hknweb.forms import CsvImportForm
12+
13+
14+
@admin.register(BitByteGroup)
15+
class BitByteGroupAdmin(admin.ModelAdmin):
16+
change_list_template = "admin/candidate/bitbyte_group_change_list.html"
17+
18+
fields = ["semester", "bytes", "bits"]
19+
list_display = (
20+
"semester",
21+
"bytes_usernames",
22+
"bits_usernames",
23+
)
24+
list_filter = ["semester"]
25+
search_fields = [
26+
"bytes__username",
27+
"bytes__first_name",
28+
"bytes__last_name",
29+
"bits__username",
30+
"bits__first_name",
31+
"bits__last_name",
32+
]
33+
autocomplete_fields = ["bytes", "bits"]
34+
35+
def bytes_usernames(self, obj):
36+
return ", ".join([user.username for user in obj.bytes.all()])
37+
38+
def bits_usernames(self, obj):
39+
return ", ".join([user.username for user in obj.bits.all()])
40+
41+
def get_urls(self):
42+
urls = super().get_urls()
43+
custom_urls = [
44+
path("import-csv/", self.import_csv, name="import_csv"),
45+
]
46+
return custom_urls + urls
47+
48+
# Refactor and move this outside of admin panel if this becomes front facing feature(i.e. you
49+
# want evp creating bitbyte groups)
50+
def import_csv(self, request):
51+
# Helper function.
52+
def _get_user_or_error(username):
53+
user = User.objects.filter(username__iexact=username).first()
54+
if not user:
55+
raise ValueError(
56+
f"Error on trying to add '{username}'."
57+
f" Check if the username is correct."
58+
)
59+
return user
60+
61+
if request.method == "POST":
62+
form = CsvImportForm(request.POST, request.FILES)
63+
if form.is_valid():
64+
csv_file = request.FILES["csv_file"]
65+
data_set = csv_file.read().decode("UTF-8")
66+
io_string = io.StringIO(data_set)
67+
next(io_string) # Skip header
68+
69+
try:
70+
# If an error happens we just don't do anything
71+
with transaction.atomic():
72+
count = 0
73+
for row in csv.reader(io_string, delimiter=";"):
74+
if not row or len(row) != 4:
75+
continue
76+
77+
byte_usernames, bit_usernames, year, sem = [
78+
s.strip() for s in row
79+
]
80+
81+
semester_obj = Semester.objects.get(year=year, semester=sem)
82+
group = BitByteGroup.objects.create(semester=semester_obj)
83+
for byte in byte_usernames.split(","):
84+
byte_user = _get_user_or_error(byte)
85+
group.bytes.add(byte_user)
86+
87+
for bit in bit_usernames.split(","):
88+
bit_user = _get_user_or_error(bit)
89+
group.bits.add(bit_user)
90+
91+
count += 1
92+
self.message_user(
93+
request, f"Successfully imported {count} relationships."
94+
)
95+
return redirect("..")
96+
except Exception as e:
97+
self.message_user(request, f"Error: {e}", level=messages.ERROR)
98+
99+
form = CsvImportForm()
100+
payload = {"form": form}
101+
return render(request, "admin/candidate/bitbyte_group_csv_upload.html", payload)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Generated by Django 4.2.17 on 2026-02-10 18:16
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
import django.db.models.deletion
6+
7+
8+
class Migration(migrations.Migration):
9+
dependencies = [
10+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
11+
("coursesemester", "0002_auto_20210202_0225"),
12+
("candidate", "0015_logistics_mandatory_events"),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name="BitByteGroup",
18+
fields=[
19+
(
20+
"id",
21+
models.AutoField(
22+
auto_created=True,
23+
primary_key=True,
24+
serialize=False,
25+
verbose_name="ID",
26+
),
27+
),
28+
(
29+
"bits",
30+
models.ManyToManyField(
31+
related_name="bitbyte_groups_as_bit",
32+
to=settings.AUTH_USER_MODEL,
33+
),
34+
),
35+
(
36+
"bytes",
37+
models.ManyToManyField(
38+
related_name="bitbyte_groups_as_byte",
39+
to=settings.AUTH_USER_MODEL,
40+
),
41+
),
42+
(
43+
"semester",
44+
models.ForeignKey(
45+
on_delete=django.db.models.deletion.CASCADE,
46+
related_name="bit_byte_groups",
47+
to="coursesemester.semester",
48+
),
49+
),
50+
],
51+
options={
52+
"verbose_name": "Bit Byte Group",
53+
"verbose_name_plural": "Bit Byte Groups",
54+
},
55+
),
56+
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from hknweb.candidate.models.bit_byte_activity import BitByteActivity
2+
from hknweb.candidate.models.bit_byte_group import BitByteGroup
23
from hknweb.candidate.models.officer_challenge import OffChallenge
34
from hknweb.candidate.models.announcement import Announcement
45
from hknweb.candidate.models.logistics import Logistics, EventReq, MiscReq, FormReq

hknweb/candidate/models/bit_byte_activity.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from django.db import models
2-
2+
from django.conf import settings
33
from hknweb.candidate.models.constants import MAX_STRLEN
44

55

@@ -13,7 +13,8 @@ class BitByteActivity(models.Model):
1313
class Meta:
1414
verbose_name_plural = "Bit Byte Activities"
1515

16-
participants = models.ManyToManyField("auth.User")
16+
# TODO: Switch to use BitByteGroup model if more/proper bitbyte group backend is added.
17+
participants = models.ManyToManyField(settings.AUTH_USER_MODEL)
1718
# whether VP/Csec confirmed this request, null when unreviewed
1819
confirmed = models.BooleanField(null=True)
1920
proof = models.CharField(
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from django.db import models
2+
from django.conf import settings
3+
from hknweb.coursesemester.models import Semester
4+
5+
6+
class BitByteGroup(models.Model):
7+
"""
8+
Model for bit byte group.
9+
Each bit byte group has a semester associated with it, if it is unknown the field is none.
10+
"""
11+
12+
class Meta:
13+
verbose_name = "Bit Byte Group"
14+
verbose_name_plural = "Bit Byte Groups"
15+
16+
semester = models.ForeignKey(
17+
Semester,
18+
on_delete=models.CASCADE,
19+
related_name="bit_byte_groups",
20+
)
21+
22+
bytes = models.ManyToManyField(
23+
settings.AUTH_USER_MODEL, related_name="bitbyte_groups_as_byte"
24+
)
25+
bits = models.ManyToManyField(
26+
settings.AUTH_USER_MODEL, related_name="bitbyte_groups_as_bit"
27+
)
28+
29+
def __str__(self):
30+
return (
31+
f"Bit byte group {self.semester}; "
32+
+ f"Bytes: {', '.join([c.username for c in self.bytes.all()])}; "
33+
+ f"Bits: {', '.join([c.username for c in self.bits.all()])}"
34+
)

hknweb/candidate/models/officer_challenge.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
from django.conf import settings
21
from django.db import models
32

3+
from django.conf import settings
44
from hknweb.candidate.models.constants import MAX_STRLEN
55

66

@@ -22,15 +22,15 @@ class Meta:
2222
# the requester needs permission to add officer challenges. This limits the choices in a dropdown,
2323
# but the view CandRequestView is still able to set requesters regardless of their permissions
2424
requester = models.ForeignKey(
25-
"auth.User",
25+
settings.AUTH_USER_MODEL,
2626
# NOTE: for some reason this causes an error when you try to save on the admin, so I'll just comment it out for now
2727
# limit_choices_to=Q(groups__permissions__codename='add_offchallenge'),
2828
on_delete=models.CASCADE,
2929
default=None,
3030
related_name="received_challenges",
3131
)
3232
officer = models.ForeignKey(
33-
"auth.User",
33+
settings.AUTH_USER_MODEL,
3434
limit_choices_to={"groups__name": settings.OFFICER_GROUP},
3535
on_delete=models.CASCADE,
3636
default=None,

hknweb/forms.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,7 @@ def generate_password() -> str:
267267

268268
# Save information for adding messages
269269
self.invalid_emails = invalid_emails
270+
271+
272+
class CsvImportForm(forms.Form):
273+
csv_file = forms.FileField(label="Select .csv file")

0 commit comments

Comments
 (0)