API
User Dashboard module
serializers
ModerationCommentSerializer
Bases: ModelSerializer
Source code in apps/userdashboard/serializers.py
class ModerationCommentSerializer(serializers.ModelSerializer):
comment_url = serializers.SerializerMethodField()
is_unread = serializers.SerializerMethodField()
is_modified = serializers.SerializerMethodField()
last_edit = serializers.SerializerMethodField()
moderator_feedback = ModeratorCommentFeedbackSerializer(read_only=True)
num_reports = serializers.SerializerMethodField()
feedback_api_url = serializers.SerializerMethodField()
user_name = serializers.SerializerMethodField()
user_image = serializers.SerializerMethodField()
user_profile_url = serializers.SerializerMethodField()
class Meta:
model = Comment
fields = [
"comment",
"comment_url",
"feedback_api_url",
"is_unread",
"is_blocked",
"is_moderator_marked",
"is_modified",
"last_edit",
"moderator_feedback",
"num_reports",
"pk",
"user_image",
"user_name",
"user_profile_url",
]
def get_comment_url(self, instance):
return instance.get_absolute_url()
def get_is_modified(self, comment):
return comment.modified is not None
def get_last_edit(self, comment):
if comment.modified:
return get_date_display(comment.modified)
else:
return get_date_display(comment.created)
def get_feedback_api_url(self, comment):
return reverse("moderatorfeedback-list", kwargs={"comment_pk": comment.pk})
def get_num_reports(self, comment):
return comment.num_reports
def get_user_name(self, comment):
if comment.is_censored or comment.is_removed:
return _("unknown user")
return str(comment.creator.username)
def get_user_image_fallback(self, comment):
"""Load small thumbnail images for default user images."""
if comment.is_censored or comment.is_removed:
return None
try:
if comment.creator.avatar_fallback:
return comment.creator.avatar_fallback
except AttributeError:
pass
return None
def get_user_image(self, comment):
"""Load small thumbnail images for user images."""
if comment.is_censored or comment.is_removed:
return None
try:
if comment.creator.avatar:
avatar = get_thumbnailer(comment.creator.avatar)["avatar"]
return avatar.url
except AttributeError:
pass
return self.get_user_image_fallback(comment)
def get_user_profile_url(self, comment):
if comment.is_censored or comment.is_removed:
return ""
try:
return comment.creator.get_absolute_url()
except AttributeError:
return ""
def get_is_unread(self, comment):
return not comment.is_reviewed
def update(self, instance, validated_data):
"""Update comment instance without changing comment.modified.
This is essentially copied from
rest_framework.serializers.ModelSerializer.update(),
only difference is ignore_modified=true when saving the instance.
See also here:
https://github.com/encode/django-rest-framework/blob/master/rest_framework/serializers.py#L991-L1015
"""
raise_errors_on_nested_writes("update", self, validated_data)
info = model_meta.get_field_info(instance)
# Simply set each attribute on the instance, and then save it.
# Note that unlike `.create()` we don't need to treat many-to-many
# relationships as being a special case. During updates we already
# have an instance pk for the relationships to be associated with.
m2m_fields = []
for attr, value in validated_data.items():
if attr in info.relations and info.relations[attr].to_many:
m2m_fields.append((attr, value))
else:
setattr(instance, attr, value)
instance.save(ignore_modified=True)
# Note that many-to-many fields are set after updating instance.
# Setting m2m fields triggers signals which could potentially change
# updated instance and we do not want it to collide with .update()
for attr, value in m2m_fields:
field = getattr(instance, attr)
field.set(value)
return instance
get_user_image(comment)
Load small thumbnail images for user images.
Source code in apps/userdashboard/serializers.py
def get_user_image(self, comment):
"""Load small thumbnail images for user images."""
if comment.is_censored or comment.is_removed:
return None
try:
if comment.creator.avatar:
avatar = get_thumbnailer(comment.creator.avatar)["avatar"]
return avatar.url
except AttributeError:
pass
return self.get_user_image_fallback(comment)
get_user_image_fallback(comment)
Load small thumbnail images for default user images.
Source code in apps/userdashboard/serializers.py
def get_user_image_fallback(self, comment):
"""Load small thumbnail images for default user images."""
if comment.is_censored or comment.is_removed:
return None
try:
if comment.creator.avatar_fallback:
return comment.creator.avatar_fallback
except AttributeError:
pass
return None
update(instance, validated_data)
Update comment instance without changing comment.modified.
This is essentially copied from rest_framework.serializers.ModelSerializer.update(), only difference is ignore_modified=true when saving the instance. See also here: https://github.com/encode/django-rest-framework/blob/master/rest_framework/serializers.py#L991-L1015
Source code in apps/userdashboard/serializers.py
def update(self, instance, validated_data):
"""Update comment instance without changing comment.modified.
This is essentially copied from
rest_framework.serializers.ModelSerializer.update(),
only difference is ignore_modified=true when saving the instance.
See also here:
https://github.com/encode/django-rest-framework/blob/master/rest_framework/serializers.py#L991-L1015
"""
raise_errors_on_nested_writes("update", self, validated_data)
info = model_meta.get_field_info(instance)
# Simply set each attribute on the instance, and then save it.
# Note that unlike `.create()` we don't need to treat many-to-many
# relationships as being a special case. During updates we already
# have an instance pk for the relationships to be associated with.
m2m_fields = []
for attr, value in validated_data.items():
if attr in info.relations and info.relations[attr].to_many:
m2m_fields.append((attr, value))
else:
setattr(instance, attr, value)
instance.save(ignore_modified=True)
# Note that many-to-many fields are set after updating instance.
# Setting m2m fields triggers signals which could potentially change
# updated instance and we do not want it to collide with .update()
for attr, value in m2m_fields:
field = getattr(instance, attr)
field.set(value)
return instance
views
UserDashboardActivitiesView
Bases: UserDashboardBaseMixin
Source code in apps/userdashboard/views.py
class UserDashboardActivitiesView(UserDashboardBaseMixin):
template_name = "a4_candy_userdashboard/userdashboard_activities.html"
menu_item = "overview"
@property
def actions(self):
"""Return comment/feedback actions that are on content the user created.
Do not return actions on comments for polls and documents to not spam
initiators.
"""
user = self.request.user
comment_actions = (
Action.objects.filter(
obj_content_type=ContentType.objects.get_for_model(Comment),
verb="add",
target_creator=user,
)
.exclude(
target_content_type__in=[
ContentType.objects.get_for_model(Poll),
ContentType.objects.get_for_model(Chapter),
ContentType.objects.get_for_model(Paragraph),
]
)
.exclude(
actor=user,
)
.select_related("actor", "project")
.prefetch_related("obj", "target__creator")
)
filtered_comment_actions = [
action for action in comment_actions if not action.obj.is_blocked
]
feedback_actions = (
Action.objects.filter(
obj_content_type=ContentType.objects.get_for_model(
ModeratorCommentFeedback
),
obj_comment_creator=user,
)
.exclude(actor=user)
.select_related("project")
.prefetch_related("obj__comment__creator")
)
return sorted(
filtered_comment_actions + list(feedback_actions),
key=lambda action: action.timestamp,
reverse=True,
)
actions
property
Return comment/feedback actions that are on content the user created.
Do not return actions on comments for polls and documents to not spam initiators.
UserDashboardBaseMixin
Bases: LoginRequiredMixin
, ContextMixin
, TemplateResponseMixin
, View
Adds followed projects and organisations as properties.
To be used in the user dashboard views, as they all need this info.
Source code in apps/userdashboard/views.py
class UserDashboardBaseMixin(
LoginRequiredMixin,
generic.base.ContextMixin,
generic.base.TemplateResponseMixin,
generic.base.View,
):
"""
Adds followed projects and organisations as properties.
To be used in the user dashboard views, as they all need this info.
"""
model = User
def get(self, request):
response = self.render_to_response(self.get_context_data())
return response
@property
def organisations(self):
return Organisation.objects.filter(
project__follow__creator=self.request.user, project__follow__enabled=True
).distinct()
@property
def projects(self):
projects = Project.objects.filter(
follow__creator=self.request.user, follow__enabled=True
)
return projects
UserDashboardOverviewView
Bases: UserDashboardBaseMixin
Source code in apps/userdashboard/views.py
class UserDashboardOverviewView(UserDashboardBaseMixin):
template_name = "a4_candy_userdashboard/userdashboard_overview.html"
menu_item = "overview"
@property
def actions(self):
"""Return comment/feedback actions that are on content the user created.
Do not return actions on comments for polls and documents to not spam
initiators.
"""
user = self.request.user
comment_actions = (
Action.objects.filter(
obj_content_type=ContentType.objects.get_for_model(Comment),
verb="add",
target_creator=user,
)
.exclude(
target_content_type__in=[
ContentType.objects.get_for_model(Poll),
ContentType.objects.get_for_model(Chapter),
ContentType.objects.get_for_model(Paragraph),
]
)
.exclude(actor=user)
.select_related("actor", "project")
.prefetch_related("obj", "target__creator")
)
filtered_comment_actions = [
action for action in comment_actions if not action.obj.is_blocked
]
feedback_actions = (
Action.objects.filter(
obj_content_type=ContentType.objects.get_for_model(
ModeratorCommentFeedback
),
obj_comment_creator=user,
)
.exclude(actor=user)
.select_related("project", "project__organisation")
.prefetch_related("obj__comment__creator", "obj__comment__content_object")
)
return sorted(
filtered_comment_actions + list(feedback_actions),
key=lambda action: action.timestamp,
reverse=True,
)
@property
def projects_carousel(self):
(
sorted_active_projects,
sorted_future_projects,
sorted_past_projects,
) = self.request.user.get_projects_follow_list()
projects = (
list(sorted_active_projects)
+ list(sorted_future_projects)
+ list(sorted_past_projects)
)[:8]
return projects
actions
property
Return comment/feedback actions that are on content the user created.
Do not return actions on comments for polls and documents to not spam initiators.
users and permissions
We enabled token authentification in a+. So to use the APIs, the user token has to be given instead of name and password.
To generate or get the token in the terminal:
curl -X POST http://localhost:8004/api/login/ -H 'Accept: application/json;' -d 'username=$username&password=$password'
{"token":"$some_quite_long_token"}
Idea API
To create, update and delete ideas from the app, we added an API for ideas.
To use it to add an idea:
curl -X POST http://localhost:8004/api/modules/$moduleId/ideas/ -H 'Accept: application/json;' -H 'Authorization: Token $some_quite_long_token' -d 'name=my name&description=my description'
If the idea, requires a category, use it with the pk of the category:
curl -X POST http://localhost:8004/api/modules/$moduleId/ideas/ -H 'Accept: application/json;' -H 'Authorization: Token $some_quite_long_token' -d 'name=my name&description=my description&category=1'
To post many ideas very quickly, use a shell-script like this one:
for i in {1..10}
do
curl -X POST http://localhost:8004/api/modules/$moduleId/ideas/ -H 'Accept: application/json;' -H 'Authorization: Token $some_quite_long_token' -d 'name=my name&description=my description&category=1'
done