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

Added Feature to recommend Users from profile page #2948

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 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
Binary file added .DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ repos:
rev: v0.1.13
hooks:
- id: ruff
args:
args:
- --fix
- id: ruff-format

Expand Down
4 changes: 4 additions & 0 deletions blt/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@
invite_friend,
profile,
profile_edit,
recommend_user,
recommend_via_blurb,
referral_signup,
stripe_connected,
update_bch_address,
Expand Down Expand Up @@ -625,6 +627,8 @@
path("time-logs/", TimeLogListView, name="time_logs"),
path("sizzle-daily-log/", sizzle_daily_log, name="sizzle_daily_log"),
path("blog/", include("blog.urls")),
path("recommend/<int:user_id>/", recommend_user, name="recommend_user"),
Copy link
Collaborator

Choose a reason for hiding this comment

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

can you be consistent and use either user id or username - and do we need 3 more urls? Can we do this all with one more?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

each of the 3 urls determine, recommend users manually(using the dedicated recommendation feature on user profile) , recommend directly from the button under the recommendation blurb and another is ajax recommendation(this was optional and can be removed)

path("recommend/<str:username>/blurb/", recommend_via_blurb, name="recommend_via_blurb"),
path(
"user-sizzle-report/<str:username>/",
user_sizzle_report,
Expand Down
10 changes: 10 additions & 0 deletions website/admin.py
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is missing imports

Copy link
Contributor Author

Choose a reason for hiding this comment

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

umm, can you please tell me which imports are missing from this file? I apologise if I missed any

Copy link
Collaborator

Choose a reason for hiding this comment

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

you can check the original here it has the missing import - https://github.com/OWASP-BLT/BLT/pull/2656/files - did you test this code? This would have thrown an error.

Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ def short_description(self, obj):
admin.site.unregister(User)


class RecommendationAdmin(admin.ModelAdmin):
list_display = ("recommender", "recommended_user", "created_at")
search_fields = ("recommender__username", "recommended_user__username")


class UserAdmin(ImportExportModelAdmin):
resource_class = UserResource
list_display = (
Expand Down Expand Up @@ -245,6 +250,8 @@ class UserProfileAdmin(admin.ModelAdmin):
"flagged_count",
"subscribed_domains_count",
"subscribed_users_count",
"recommendation_count",
"recommendation_blurb",
"x_username",
"linkedin_url",
"github_url",
Expand Down Expand Up @@ -273,6 +280,9 @@ def subscribed_domains_count(self, obj):
def subscribed_users_count(self, obj):
return obj.subscribed_users.count()

def recommendation_count(self, obj):
return obj.recommendations.count()


class IssueScreenshotAdmin(admin.ModelAdmin):
model = IssueScreenshot
Expand Down
9 changes: 4 additions & 5 deletions website/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from datetime import datetime

from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.contrib.sites.shortcuts import get_current_site
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.files.storage import default_storage
Expand Down Expand Up @@ -68,12 +69,10 @@ class UserIssueViewSet(viewsets.ModelViewSet):
http_method_names = ["get", "head"]

def get_queryset(self):
anonymous_user = self.request.user.is_anonymous
user_id = self.request.user.id
if anonymous_user:
return Issue.objects.exclude(Q(is_hidden=True))
if isinstance(self.request.user, AnonymousUser):
return Issue.objects.exclude(is_hidden=True)
else:
return Issue.objects.exclude(Q(is_hidden=True) & ~Q(user_id=user_id))
return Issue.objects.exclude(Q(is_hidden=True) & ~Q(user_id=self.request.user.id))


class UserProfileViewSet(viewsets.ModelViewSet):
Expand Down
20 changes: 20 additions & 0 deletions website/forms.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import re

from captcha.fields import CaptchaField
from django import forms
from mdeditor.fields import MDTextFormField
Expand Down Expand Up @@ -26,13 +28,31 @@ class Meta:
"discounted_hourly_rate",
"github_url",
"role",
"recommendation_blurb",
]
widgets = {
"tags": forms.CheckboxSelectMultiple(),
"subscribed_domains": forms.CheckboxSelectMultiple(),
"subscribed_users": forms.CheckboxSelectMultiple(),
"recommendation_blurb": forms.Textarea(
attrs={
"rows": "10",
"class": "mt-2 block w-full py-3 px-4 text-base border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500",
"placeholder": "Write your recommendation blurb here...",
}
),
}

def clean_recommendation_blurb(self):
blurb = self.cleaned_data.get("recommendation_blurb")
# Remove any potential template tags or code that might have been entered
if blurb:
# Remove any HTML or template tags
blurb = re.sub(r"<[^>]+>", "", blurb)
tsu-ki marked this conversation as resolved.
Show resolved Hide resolved
blurb = re.sub(r"{%.*?%}", "", blurb)

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data

This [regular expression](1) that depends on a [user-provided value](2) may run slow on strings starting with '{{%' and with many repetitions of '{{%'.
blurb = re.sub(r"{{.*?}}", "", blurb)

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data

This [regular expression](1) that depends on a [user-provided value](2) may run slow on strings starting with '{{{{' and with many repetitions of '{{{{'.
return blurb

# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
# print("UserProfileForm __init__")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("website", "0153_delete_contributorstats"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name="Recommendation",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
(
"recommended_user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="received_recommendations",
to=settings.AUTH_USER_MODEL,
),
),
(
"recommender",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="given_recommendations",
to=settings.AUTH_USER_MODEL,
),
),
],
),
migrations.AddConstraint(
model_name="recommendation",
constraint=models.UniqueConstraint(
fields=("recommender", "recommended_user"), name="unique_recommendation"
),
),
]
17 changes: 17 additions & 0 deletions website/migrations/0155_userprofile_recommendation_blurb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 5.1.3 on 2024-11-21 19:24

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("website", "0154_recommendation_recommendation_unique_recommendation"),
]

operations = [
migrations.AddField(
model_name="userprofile",
name="recommendation_blurb",
field=models.TextField(blank=True, null=True),
),
]
19 changes: 19 additions & 0 deletions website/migrations/0156_userprofile_recommended_by.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 5.1.3 on 2024-11-23 07:05

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("website", "0155_userprofile_recommendation_blurb"),
]

operations = [
migrations.AddField(
model_name="userprofile",
name="recommended_by",
field=models.ManyToManyField(
blank=True, related_name="has_recommended", to="website.userprofile"
),
),
]
34 changes: 34 additions & 0 deletions website/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,13 +509,25 @@ class UserProfile(models.Model):
title = models.IntegerField(choices=title, default=0)
role = models.CharField(max_length=255, blank=True, null=True)
description = models.TextField(blank=True, null=True)
recommendation_blurb = models.TextField(blank=True, null=True)
winnings = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
issue_upvoted = models.ManyToManyField(Issue, blank=True, related_name="upvoted")
issue_downvoted = models.ManyToManyField(Issue, blank=True, related_name="downvoted")
issue_saved = models.ManyToManyField(Issue, blank=True, related_name="saved")
issue_flaged = models.ManyToManyField(Issue, blank=True, related_name="flaged")
issues_hidden = models.BooleanField(default=False)

recommended_by = models.ManyToManyField(
"self", symmetrical=False, related_name="has_recommended", blank=True
)

@property
def recommendation_count(self):
# Count both types of recommendations
standard_recommendations = Recommendation.objects.filter(recommended_user=self.user).count()
blurb_recommendations = self.recommended_by.count()
return standard_recommendations + blurb_recommendations

subscribed_domains = models.ManyToManyField(
Domain, related_name="user_subscribed_domains", blank=True
)
Expand Down Expand Up @@ -890,6 +902,28 @@ def __str__(self):
return f"ActivityLog by {self.user.username} at {self.recorded_at}"


class Recommendation(models.Model):
recommender = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="given_recommendations"
)
recommended_user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="received_recommendations"
)
created_at = models.DateTimeField(auto_now_add=True)

class Meta:
constraints = [
models.UniqueConstraint(
fields=["recommender", "recommended_user"], name="unique_recommendation"
)
]

def __str__(self):
return (
f"Recommendation from {self.recommender.username} to {self.recommended_user.username}"
)


class DailyStatusReport(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
date = models.DateField()
Expand Down
1 change: 1 addition & 0 deletions website/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class Meta:
"issue_flaged",
"total_score",
"activities",
# "recommendations",
)


Expand Down
Loading