Skip to content
Merged
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
12 changes: 6 additions & 6 deletions forum/models/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,22 @@ def __init__(
self.client: MongoBackend = client or MongoBackend(collection=collection_name)

@property
def collection(self) -> Collection[Dict[str, Any]]:
def _collection(self) -> Collection[Dict[str, Any]]:
"""Get mongo db collection"""
return self.client.collection
return self.get_client.collection

@property
def get_client(self) -> MongoBackend:
"""Get mongo client"""
return self.client

def get(self, **kwargs: Any) -> Optional[Dict[str, Any]]:
def get(self, _id: str) -> Optional[Dict[str, Any]]:
"""Get a document by filter"""
return self.collection.find_one(kwargs)
return self._collection.find_one({"_id": _id})

def list(self, **kwargs: Any) -> Cursor[Dict[str, Any]]:
"""Get a list of all documents filtered by kwargs"""
return self.collection.find(kwargs)
return self._collection.find(kwargs)

@abstractmethod
def insert(self, *args: Any, **kwargs: Any) -> str:
Expand All @@ -55,7 +55,7 @@ def delete(self, _id: str) -> int:
Returns:
The number of documents deleted.
"""
result = self.collection.delete_one({"_id": ObjectId(_id)})
result = self._collection.delete_one({"_id": ObjectId(_id)})
return result.deleted_count

@abstractmethod
Expand Down
72 changes: 58 additions & 14 deletions forum/models/contents.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Content Class for mongo backend."""

from typing import Any, Dict, Optional
from typing import Any, Dict, List, Optional

from bson import ObjectId

from forum.models.base_model import MongoBaseModel
from forum.mongo import MongoBackend
Expand All @@ -25,19 +27,15 @@ def __init__(
"""
super().__init__(collection_name, client)

def get(self, **kwargs: Any) -> Optional[Dict[str, Any]]:
def get(self, _id: str) -> Optional[Dict[str, Any]]: # pylint: disable=arguments-differ
"""
Retrieves a content document from the database based on provided arguments.

Retrieves a contents document from the database based on the provided _id.
Args:
kwargs: The filter arguments.

_id: The ObjectId of the contents document to retrieve.
Returns:
The thread contents if found, otherwise None.
The contents document if found, otherwise None.
"""
if self.content_type:
kwargs["_type"] = self.content_type
return self.collection.find_one(kwargs)
return self._collection.find_one({"_id": ObjectId(_id)})

def list(self, **kwargs: Any) -> Any:
"""
Expand All @@ -51,10 +49,56 @@ def list(self, **kwargs: Any) -> Any:
"""
if self.content_type:
kwargs["_type"] = self.content_type
return self.collection.find(kwargs)
return self._collection.find(kwargs)

def insert(self, *args: Any, **kwargs: Any) -> str:
def insert( # pylint: disable=arguments-differ
self,
_id: str,
author_id: str,
abuse_flaggers: List[str],
historical_abuse_flaggers: List[str],
visible: bool,
) -> str:
"""
Return not implemented error on the insert.
Inserts a new content document into the database.

Args:
_id (str): The ID of the content.
author_id (str): The ID of the author who created the content.
abuse_flaggers (List[str]): A list of IDs of users who flagged the content as abusive.
historical_abuse_flaggers (List[str]): A list of IDs of users who previously flagged the content as abusive.
visible (bool): Whether the content is visible or not.

Returns:
str: The ID of the inserted document.
"""
raise NotImplementedError
content_data = {
"_id": ObjectId(_id),
"author_id": author_id,
"abuse_flaggers": abuse_flaggers,
"historical_abuse_flaggers": historical_abuse_flaggers,
"visible": visible,
}
result = self._collection.insert_one(content_data)
return str(result.inserted_id)

def update(self, _id: str, **kwargs: Any) -> int: # pylint: disable=arguments-differ
"""
Updates a contents document in the database based on the provided _id.

Args:
_id: The id of the contents document to update.
**kwargs: The fields to update in the contents document.

Returns:
The number of documents modified.
"""
update_data = {}

update_data["abuse_flaggers"] = kwargs.get("abuse_flaggers")

result = self._collection.update_one(
{"_id": ObjectId(_id)},
{"$set": update_data},
)
return result.modified_count
86 changes: 86 additions & 0 deletions forum/models/model_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""Model util function for db operations."""

from typing import Any, Dict, Union

from forum.models.contents import Contents
from forum.models.users import Users


def flag_as_abuse(user: Dict[str, Any], entity: Dict[str, Any]) -> Union[Dict[str, Any], None]:
"""
Flag an entity as abuse.

Args:
user (Dict[str, Any]): The user who is flagging the entity as abuse.
entity (Dict[str, Any]): The entity being flagged as abuse.

Returns:
Dict[str, Any]: The updated entity with the abuse flag.

Raises:
ValueError: If user ID or entity is not provided.
"""

abuse_flaggers = entity["abuse_flaggers"]
if user["_id"] not in abuse_flaggers:
abuse_flaggers.append(user["_id"])
Contents().update(
entity["_id"],
abuse_flaggers=abuse_flaggers,
)

# Check if this is the first abuse flag
first_flag_added = len(entity["abuse_flaggers"]) == 1

# If this is the first abuse flag, update author's stats
active_flags = user.get("active_flags", 0) + 1
if first_flag_added:
Users().update(
entity["author_id"],
active_flags=active_flags,
)

# Reload the object and return it as a JSON string
return Contents().get(entity["_id"])


def un_flag_as_abuse(user: Dict[str, Any], entity: Dict[str, Any]) -> Union[Dict[str, Any], None]:
"""
Unflag an entity as abuse.

Args:
user (Dict[str, Any]): The user who is unflagging the entity as abuse.
entity (Dict[str, Any]): The entity being unflagged as abuse.

Returns:
Dict[str, Any]: The updated entity with the abuse flag removed.

Raises:
ValueError: If user ID or entity is not provided.
"""
if user["_id"] in entity["abuse_flaggers"]:
entity["abuse_flaggers"].remove(user["_id"])
Contents().update(
entity["_id"],
abuse_flaggers=entity["abuse_flaggers"],
)
# TODO: Update course stats for abuse.
return Contents().get(entity["_id"])


def un_flag_all_as_abuse(entity: Dict[str, Any]) -> Union[Dict[str, Any], None]:
"""
Unflag an entity as abuse for all users.

Args:
entity (Dict[str, Any]): The entity being unflagged as abuse.

Returns:
Dict[str, Any]: The updated entity with all abuse flags removed.

Raises:
ValueError: If entity is not provided.
"""
Contents().update(entity["_id"], abuse_flaggers=[])
# TODO: Update course stats for abuse.
return Contents().get(entity["_id"])
8 changes: 4 additions & 4 deletions forum/models/threads.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def get_votes(
}
return votes

def insert(
def insert( # type: ignore
self,
title: str,
body: str,
Expand Down Expand Up @@ -112,10 +112,10 @@ def insert(
"updated_at": date,
"last_activity_at": date,
}
result = self.collection.insert_one(thread_data)
result = self._collection.insert_one(thread_data)
return str(result.inserted_id)

def update(
def update( # type: ignore
self,
thread_id: str,
thread_type: Optional[str] = None,
Expand Down Expand Up @@ -191,7 +191,7 @@ def update(
date = datetime.now()
update_data["updated_at"] = date
update_data["last_activity_at"] = date
result = self.collection.update_one(
result = self._collection.update_one(
{"_id": ObjectId(thread_id)},
{"$set": update_data},
)
Expand Down
21 changes: 13 additions & 8 deletions forum/models/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def insert(
"read_states": read_states,
"course_stats": course_stats,
}
result = self.collection.insert_one(user_data)
result = self._collection.insert_one(user_data)
return str(result.inserted_id)

def delete(self, _id: Any) -> int:
Expand All @@ -73,7 +73,7 @@ def delete(self, _id: Any) -> int:
The number of documents deleted.

"""
result = self.collection.delete_one({"_id": _id})
result = self._collection.delete_one({"_id": _id})
return result.deleted_count

def update(
Expand All @@ -84,17 +84,21 @@ def update(
default_sort_key: Optional[str] = None,
read_states: Optional[List[Dict[str, Any]]] = None,
course_stats: Optional[List[Dict[str, Any]]] = None,
active_flags: Optional[int] = None,
) -> int:
"""
Updates a user document in the database based on the external_id.

Args:
external_id: The external ID of the user.
username: The new username of the user.
email: The new email of the user.
default_sort_key: The new default sort key for the user.
read_states: The new read states of the user.
course_stats: The new course statistics of the user.
**kwargs: Keyword arguments to update the user document.
Supported keys:
- username: The new username of the user.
- email: The new email of the user.
- default_sort_key: The new default sort key for the user.
- read_states: The new read states of the user.
- course_stats: The new course statistics of the user.
- active_flags: The new active flags of the user.

Returns:
The number of documents modified.
Expand All @@ -106,12 +110,13 @@ def update(
("default_sort_key", default_sort_key),
("read_states", read_states),
("course_stats", course_stats),
("active_flags", active_flags),
]
update_data: dict[str, Any] = {
field: value for field, value in fields if value is not None
}

result = self.collection.update_one(
result = self._collection.update_one(
{"external_id": external_id},
{"$set": update_data},
)
Expand Down
62 changes: 62 additions & 0 deletions forum/serializers/contents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Serializer class for content collection."""

from datetime import datetime
from typing import Any, Dict, List, Optional

from rest_framework import serializers

from forum.serializers.votes import VotesSerializer


class ContentSerializer(serializers.Serializer[Dict[str, Any]]):
"""
Serializer for the content data.

Attributes:
_id (CharField): The ID of the content.
votes (VotesSerializer): The votes data for the content.
visible (BooleanField): Whether the content is visible.
abuse_flaggers (ListField): List of user IDs who flagged the content for abuse.
historical_abuse_flaggers (ListField): List of user IDs who historically flagged the content for abuse.
parent_ids (ListField): List of parent IDs for the content.
at_position_list (ListField): List of positions for the content.
body (CharField): The body text of the content.
course_id (CharField): The ID of the course associated with the content.
_type (CharField): The type of content.
endorsed (BooleanField): Whether the content is endorsed.
anonymous (BooleanField): Whether the content is anonymous.
anonymous_to_peers (BooleanField): Whether the content is anonymous to peers.
parent_id (CharField): The ID of the parent content.
author_id (CharField): The ID of the author of the content.
comment_thread_id (CharField): The ID of the comment thread associated with the content.
child_count (IntegerField): The number of child comments.
depth (IntegerField): The depth of the content in the comment thread.
author_username (CharField): The username of the author of the content.
sk (CharField): The sorting key for the content.
updated_at (DateTimeField): The date and time the content was last updated.
created_at (DateTimeField): The date and time the content was created.
"""

_id = serializers.CharField()
votes = VotesSerializer(allow_null=True)
visible = serializers.BooleanField(allow_null=True)
abuse_flaggers = serializers.ListField(child=serializers.CharField(), allow_null=True)
historical_abuse_flaggers = \
serializers.ListField(child=serializers.CharField(), allow_null=True)
parent_ids = serializers.ListField(child=serializers.CharField(), allow_null=True)
at_position_list = serializers.ListField(allow_null=True)
body = serializers.CharField(allow_null=True)
course_id = serializers.CharField(allow_null=True)
_type = serializers.CharField(allow_null=True)
endorsed = serializers.BooleanField(allow_null=True)
anonymous = serializers.BooleanField(allow_null=True)
anonymous_to_peers = serializers.BooleanField(allow_null=True)
parent_id = serializers.CharField(allow_null=True)
author_id = serializers.CharField(allow_null=True)
comment_thread_id = serializers.CharField(allow_null=True)
child_count = serializers.IntegerField(allow_null=True)
depth = serializers.IntegerField(allow_null=True)
author_username = serializers.CharField(allow_null=True)
sk = serializers.CharField(allow_null=True)
updated_at = serializers.DateTimeField(allow_null=True)
created_at = serializers.DateTimeField(allow_null=True)
Loading