Skip to content
Open
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: 1 addition & 0 deletions hknweb/candidate/admin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from hknweb.candidate.admin.announcement import AnnouncementAdmin
from hknweb.candidate.admin.bitbyteactivity import BitByteActivityAdmin
from hknweb.candidate.admin.bit_byte_group import BitByteGroupAdmin
from hknweb.candidate.admin.officer_challenge import OffChallengeAdmin
from hknweb.candidate.admin.logistics import LogisticsAdmin
101 changes: 101 additions & 0 deletions hknweb/candidate/admin/bit_byte_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from django.contrib import admin, messages
from django.shortcuts import render, redirect
from django.urls import path
from django.db import transaction
from django.contrib.auth.models import User

import io
import csv
from hknweb.coursesemester.models import Semester
from hknweb.candidate.models import BitByteGroup
from hknweb.forms import CsvImportForm


@admin.register(BitByteGroup)
class BitByteGroupAdmin(admin.ModelAdmin):
change_list_template = "admin/candidate/bitbyte_group_change_list.html"

fields = ["semester", "bytes", "bits"]
list_display = (
"semester",
"bytes_usernames",
"bits_usernames",
)
list_filter = ["semester"]
search_fields = [
"bytes__username",
"bytes__first_name",
"bytes__last_name",
"bits__username",
"bits__first_name",
"bits__last_name",
]
autocomplete_fields = ["bytes", "bits"]

def bytes_usernames(self, obj):
return ", ".join([user.username for user in obj.bytes.all()])

def bits_usernames(self, obj):
return ", ".join([user.username for user in obj.bits.all()])

def get_urls(self):
urls = super().get_urls()
custom_urls = [
path("import-csv/", self.import_csv, name="import_csv"),
]
return custom_urls + urls

# Refactor and move this outside of admin panel if this becomes front facing feature(i.e. you
# want evp creating bitbyte groups)
def import_csv(self, request):
# Helper function.
def _get_user_or_error(username):
user = User.objects.filter(username__iexact=username).first()
if not user:
raise ValueError(
f"Error on trying to add '{username}'."
f" Check if the username is correct."
)
return user

if request.method == "POST":
form = CsvImportForm(request.POST, request.FILES)
if form.is_valid():
csv_file = request.FILES["csv_file"]
data_set = csv_file.read().decode("UTF-8")
io_string = io.StringIO(data_set)
next(io_string) # Skip header

try:
# If an error happens we just don't do anything
with transaction.atomic():
count = 0
for row in csv.reader(io_string, delimiter=";"):
if not row or len(row) != 4:
continue

byte_usernames, bit_usernames, year, sem = [
s.strip() for s in row
]

semester_obj = Semester.objects.get(year=year, semester=sem)
group = BitByteGroup.objects.create(semester=semester_obj)
for byte in byte_usernames.split(","):
byte_user = _get_user_or_error(byte)
group.bytes.add(byte_user)

for bit in bit_usernames.split(","):
bit_user = _get_user_or_error(bit)
group.bits.add(bit_user)

count += 1
self.message_user(
request, f"Successfully imported {count} relationships."
)
return redirect("..")
except Exception as e:
self.message_user(request, f"Error: {e}", level=messages.ERROR)

form = CsvImportForm()
payload = {"form": form}
return render(request, "admin/candidate/bitbyte_group_csv_upload.html", payload)
58 changes: 58 additions & 0 deletions hknweb/candidate/migrations/0016_bitbytegroup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Generated by Django 4.2.17 on 2026-01-11 18:31

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),
("coursesemester", "0002_auto_20210202_0225"),
("candidate", "0015_logistics_mandatory_events"),
]

operations = [
migrations.CreateModel(
name="BitByteGroup",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"bits",
models.ManyToManyField(
related_name="bitbyte_groups_as_bit",
to=settings.AUTH_USER_MODEL,
),
),
(
"bytes",
models.ManyToManyField(
related_name="bitbyte_groups_as_byte",
to=settings.AUTH_USER_MODEL,
),
),
(
"semester",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="bit_byte_groups",
to="coursesemester.semester",
),
),
],
options={
"verbose_name": "Bit Byte Group",
"verbose_name_plural": "Bit Byte Groups",
},
),
]
1 change: 1 addition & 0 deletions hknweb/candidate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from hknweb.candidate.models.bit_byte_activity import BitByteActivity
from hknweb.candidate.models.bit_byte_group import BitByteGroup
from hknweb.candidate.models.officer_challenge import OffChallenge
from hknweb.candidate.models.announcement import Announcement
from hknweb.candidate.models.logistics import Logistics, EventReq, MiscReq, FormReq
32 changes: 32 additions & 0 deletions hknweb/candidate/models/bit_byte_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from django.db import models

from hknweb.coursesemester.models import Semester


class BitByteGroup(models.Model):
"""
Model for bit byte group.
Each bit byte group has a semester associated with it, if it is unknown the field is none.
"""

class Meta:
verbose_name = "Bit Byte Group"
verbose_name_plural = "Bit Byte Groups"

semester = models.ForeignKey(
Semester,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="bit_byte_groups",
)

bytes = models.ManyToManyField("auth.User", related_name="bitbyte_groups_as_byte")
bits = models.ManyToManyField("auth.User", related_name="bitbyte_groups_as_bit")

def __str__(self):
return (
f"Bit byte group {self.semester}; "
+ f"Bytes: {', '.join([c.username for c in self.bytes.all()])}; "
+ f"Bits: {', '.join([c.username for c in self.bits.all()])}"
)
4 changes: 4 additions & 0 deletions hknweb/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,3 +267,7 @@ def generate_password() -> str:

# Save information for adding messages
self.invalid_emails = invalid_emails


class CsvImportForm(forms.Form):
csv_file = forms.FileField(label="Select .csv file")
Loading