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
149 changes: 149 additions & 0 deletions web/management/commands/create_test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
ForumReply,
ForumTopic,
Goods,
Meme,
PeerConnection,
PeerMessage,
Points,
Expand Down Expand Up @@ -72,6 +73,7 @@ def clear_data(self):
Profile,
User,
Goods,
Meme,
ProductImage,
]
for model in models:
Expand Down Expand Up @@ -115,6 +117,8 @@ def handle(self, *args, **kwargs):
students.append(user)
self.stdout.write(f"Created student: {user.username}")

users = teachers + students

# Create challenges first
challenges = []
for i in range(5):
Expand Down Expand Up @@ -574,4 +578,149 @@ def handle(self, *args, **kwargs):
)
self.stdout.write(f"Created images for product: {product.name}")

# Create educational memes with random users
self.create_meme_test_data(subjects, users)

self.stdout.write(self.style.SUCCESS("Successfully created test data"))

def create_meme_test_data(self, subjects: list[Subject], users: list[User]) -> None:
"""Create test data for educational memes."""
self.stdout.write("Creating educational meme test data...")

# Define meme data for various subjects
meme_data = [
{
"subject_name": "Mathematics",
"description": "The study of numbers, quantities, and shapes",
"icon": "fas fa-square-root-alt",
"memes": [
{
"title": "When You Finally Solve That Math Problem",
"caption": "The feeling when you've been stuck on a calculus problem for"
" hours and finally get it right.",
"image": "memes/math_eureka.jpg",
},
{
"title": "Algebra vs. Real World Problems",
"caption": "When you can solve complex equations but can't figure out how to adult.",
"image": "memes/algebra_vs_life.jpg",
},
],
},
{
"subject_name": "Computer Science",
"description": "The study of computers and computational systems",
"icon": "fas fa-laptop-code",
"memes": [
{
"title": "Debugging Be Like",
"caption": "When you've spent all day looking for a bug and it was just a missing semicolon.",
"image": "memes/debugging_semicolon.png",
},
{
"title": "Python vs JavaScript",
"caption": "The eternal debate among programmers.",
"image": "memes/python_vs_js.png",
},
],
},
{
"subject_name": "Physics",
"description": "The study of matter, energy, and the interaction between them",
"icon": "fas fa-atom",
"memes": [
{
"title": "Newton's Third Law in Real Life",
"caption": "For every action, there is an equal and opposite reaction. "
"Especially when messing with cats.",
"image": "memes/newton_cat.gif",
},
{
"title": "Schrodinger's Cat Explained",
"caption": "When the cat is both alive and dead until you open the box"
" - quantum physics at its finest.",
"image": "memes/schrodinger_cat.jpg",
},
],
},
{
"subject_name": "Biology",
"description": "The study of living organisms",
"icon": "fas fa-dna",
"memes": [
{
"title": "Mitochondria is the Powerhouse of the Cell",
"caption": "The one thing everyone remembers from biology class.",
"image": "memes/mitochondria_powerhouse.jpg",
},
{
"title": "Evolution of Humans",
"caption": "From apes to smartphone zombies - the missing link is WiFi.",
"image": "memes/evolution_wifi.jpg",
},
],
},
{
"subject_name": "History",
"description": "The study of past events",
"icon": "fas fa-landmark",
"memes": [
{
"title": "History Students During Exams",
"caption": "When you need to remember hundreds of dates and events for your history final.",
"image": "memes/history_dates.png",
},
{
"title": "When Your History Teacher Says 'Pop Quiz'",
"caption": "Sudden panic when you realize you haven't memorized all those important dates.",
"image": "memes/history_pop_quiz.png",
},
],
},
]

# For each subject, create or get the subject and add memes
for subject_data in meme_data:
# Find or create the subject
subject_name = subject_data["subject_name"]
subject_obj = None

# Try to find the subject in the existing subjects
for s in subjects:
if s.name == subject_name:
subject_obj = s
break

# If subject doesn't exist, create it
if not subject_obj:
subject_obj = Subject.objects.create(
name=subject_name,
slug=slugify(subject_name),
description=subject_data.get("description", f"Study of {subject_name}"),
icon=subject_data.get("icon", "fas fa-graduation-cap"),
order=len(subjects) + 1,
)
subjects.append(subject_obj)
self.stdout.write(f"Created new subject: {subject_obj.name}")

# Create memes for this subject
for meme_info in subject_data["memes"]:
# Select a random user as uploader
uploader = random.choice(users)

# Generate a random date within the last month
random_date = timezone.now() - timedelta(days=random.randint(0, 30))

# Create the meme
meme = Meme.objects.create(
title=meme_info["title"],
subject=subject_obj,
caption=meme_info["caption"],
image=meme_info["image"],
uploader=uploader,
slug=slugify(meme_info["title"]),
created_at=random_date,
updated_at=random_date,
)

self.stdout.write(f"Created meme: {meme.title} (uploaded by {uploader.username})")
49 changes: 45 additions & 4 deletions web/templates/memes.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,27 @@ <h1 class="text-2xl font-bold">Educational Memes</h1>
<!-- Subject filter -->
<div class="mb-6">
<div class="flex flex-wrap items-center gap-2">
<span class="font-semibold">Filter by user:</span>
<select name="users"
id="memes-creators"
class="border rounded px-3 py-2 text-sm">
<option hidden>Select user</option>
<option value="">All users</option>
{% for key, value in memes_creators %}
<option value="{{ key }}" {% if key == selected_user_id %}selected{% endif %}>{{ value }}</option>
{% endfor %}
</select>
<span class="font-semibold">Filter by subject:</span>
<a href="{% url 'meme_list' %}"
class="px-3 py-1 rounded-full {% if not selected_subject %}bg-teal-600 text-white{% else %}bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200{% endif %}">
All
</a>
{% for subject in subjects %}
<a href="{% url 'meme_list' %}?subject={{ subject.slug }}"
class="px-3 py-1 rounded-full {% if selected_subject == subject.slug %}bg-teal-600 text-white{% else %}bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200{% endif %}">
{{ subject.name }}
</a>
<button data-value="{{ subject.slug }}" class="subject-button">
<h1 class="px-3 py-1 rounded-full {% if selected_subject == subject.slug %}bg-teal-600 text-white{% else %}bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200{% endif %}">
{{ subject.name }}
</h1>
</button>
{% endfor %}
</div>
</div>
Expand Down Expand Up @@ -83,4 +94,34 @@ <h3 class="text-lg font-bold text-gray-900 dark:text-white">{{ meme.title }}</h3
</div>
{% endif %}
</div>
<script>
let selectedSubject = "{{ selected_subject }}"
let selectedUserId = "{{ selected_user_id }}"
const baseUrl = "{% url 'meme_list' %}";

document.querySelectorAll(".subject-button").forEach(element => {
element.addEventListener("click", function() {
const selectedSubject = this.dataset.value;
// Create URL object for proper encoding of parameters
const url = new URL(baseUrl, window.location.origin);
url.searchParams.append('user', selectedUserId);
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

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

The user parameter is appended even when selectedUserId/selectedUser is empty or 'None'. This will add an empty user parameter to the URL. Consider only appending the parameter when the value is truthy and not 'None'.

Copilot uses AI. Check for mistakes.
if (selectedSubject) {
url.searchParams.append('subject', selectedSubject);
}
window.location.href = url.toString();
})
});

document.getElementById('memes-creators').addEventListener('change', function() {
const selectedUser = this.value;

// Create URL object for proper encoding of parameters
const url = new URL(baseUrl, window.location.origin);
url.searchParams.append('user', selectedUser);
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

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

The user parameter is appended even when selectedUserId/selectedUser is empty or 'None'. This will add an empty user parameter to the URL. Consider only appending the parameter when the value is truthy and not 'None'.

Suggested change
url.searchParams.append('user', selectedUser);
if (selectedUser && selectedUser !== 'None') {
url.searchParams.append('user', selectedUser);
}

Copilot uses AI. Check for mistakes.
if (selectedSubject) {
url.searchParams.append('subject', selectedSubject);
}
window.location.href = url.toString();
});
</script>
{% endblock content %}
25 changes: 23 additions & 2 deletions web/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4497,15 +4497,36 @@ def graphing_calculator(request):
def meme_list(request):
memes = Meme.objects.all().order_by("-created_at")
subjects = Subject.objects.filter(memes__isnull=False).distinct()

# Filter by subject if provided
subject_filter = request.GET.get("subject")
if subject_filter:

# Filter by user if provided
user_filter = request.GET.get("user")

# Get distinct uploaders from the database
uploaders = memes.values("uploader__id", "uploader__username").distinct()
memes_creators = {str(uploader["uploader__id"]): uploader["uploader__username"] for uploader in uploaders}
Comment on lines 4498 to +4509
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Drop the -created_at ordering before calling values().distinct().

memes still carries order_by("-created_at") when you execute memes.values(...).distinct(). On PostgreSQL this becomes SELECT DISTINCT ... ORDER BY created_at, which fails because the ordered column isn’t in the projection. As soon as someone loads the page you’ll hit a database error. Call .order_by() (with no args) before values() or build the uploader queryset off a fresh Meme.objects.order_by().values(...) so the DISTINCT runs without the invalid ORDER BY.

🤖 Prompt for AI Agents
In web/views.py around lines 4437 to 4448, the queryset `memes` still has
`.order_by("-created_at")` when you call `memes.values(...).distinct()`, which
on Postgres produces an invalid ORDER BY on columns not in the projection;
remove that ordering before calling `values().distinct()` by either calling
`memes = memes.order_by()` (no args) prior to `values(...)` or build the
uploader queryset from a fresh un-ordered queryset like
`Meme.objects.order_by().values(...).distinct()`, so the DISTINCT query runs
without the invalid ORDER BY.


if subject_filter and subject_filter != "None":
memes = memes.filter(subject__slug=subject_filter)

if user_filter and user_filter != "None":
memes = memes.filter(uploader_id=user_filter)

Comment on lines +4507 to +4516
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

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

The query for uploaders is executed before applying filters, which could be inefficient. Consider moving this query after the filtering logic to only fetch uploaders from the filtered meme set, or use a separate query on the User model to get all users who have uploaded memes.

Suggested change
# Get distinct uploaders from the database
uploaders = memes.values("uploader__id", "uploader__username").distinct()
memes_creators = {str(uploader["uploader__id"]): uploader["uploader__username"] for uploader in uploaders}
if subject_filter and subject_filter != "None":
memes = memes.filter(subject__slug=subject_filter)
if user_filter and user_filter != "None":
memes = memes.filter(uploader_id=user_filter)
if subject_filter and subject_filter != "None":
memes = memes.filter(subject__slug=subject_filter)
if user_filter and user_filter != "None":
memes = memes.filter(uploader_id=user_filter)
# Get distinct uploaders from the filtered memes queryset
uploaders = memes.values("uploader__id", "uploader__username").distinct()
memes_creators = {str(uploader["uploader__id"]): uploader["uploader__username"] for uploader in uploaders}

Copilot uses AI. Check for mistakes.
paginator = Paginator(memes, 12) # Show 12 memes per page
page_number = request.GET.get("page", 1)
page_obj = paginator.get_page(page_number)

return render(request, "memes.html", {"memes": page_obj, "subjects": subjects, "selected_subject": subject_filter})
context = {
"memes": page_obj,
"subjects": subjects,
"selected_subject": subject_filter,
"selected_user_id": user_filter,
"memes_creators": memes_creators.items(),
}

return render(request, "memes.html", context)


def meme_detail(request: HttpRequest, slug: str) -> HttpResponse:
Expand Down
Loading