Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • djangoldp-packages/djangoldp-circle
1 result
Show changes
Commits on Source (20)
Showing
with 370 additions and 24 deletions
graft djangoldp_circle graft djangoldp_circle
recursive-include djangoldp_circle/templates * recursive-include djangoldp_circle/templates *
recursive-include djangoldp_circle/locale *
global‐exclude *.py[cod] __pycache__ *.so global‐exclude *.py[cod] __pycache__ *.so
\ No newline at end of file
from django.conf.urls import url from django.conf.urls import url
from .views import CirclesJoinableViewset, CirclesRequestableViewset, CircleAccessRequestServerNotificationView from .views import CirclesJoinableViewset, CirclesRequestableViewset, CircleAccessRequestServerNotificationView, CircleAccessRequestFilteredByUserViewSet
from .models import Circle from .models import Circle, CircleAccessRequest
from djangoldp.models import Model from djangoldp.models import Model
...@@ -17,5 +17,11 @@ urlpatterns = [ ...@@ -17,5 +17,11 @@ urlpatterns = [
permission_classes=Model.get_meta(Circle, 'permission_classes', []), permission_classes=Model.get_meta(Circle, 'permission_classes', []),
fields=Model.get_meta(Circle, 'serializer_fields', []), fields=Model.get_meta(Circle, 'serializer_fields', []),
nested_fields=Model.get_meta(Circle, 'nested_fields', []))), nested_fields=Model.get_meta(Circle, 'nested_fields', []))),
url(r'circleaccessrequests/inbox/', CircleAccessRequestServerNotificationView.as_view(), name='circle-notifications-inbox') url(r'circleaccessrequests/inbox/', CircleAccessRequestServerNotificationView.as_view(), name='circle-notifications-inbox'),
url(r'circleaccessrequests/me/', CircleAccessRequestFilteredByUserViewSet.urls(model_prefix="circlesaccessrequests-me",
model=CircleAccessRequest,
lookup_field=Model.get_meta(CircleAccessRequest, 'lookup_field', 'pk'),
permission_classes=Model.get_meta(CircleAccessRequest, 'permission_classes', []),
fields=Model.get_meta(CircleAccessRequest, 'serializer_fields', []),
nested_fields=Model.get_meta(CircleAccessRequest, 'nested_fields', []))),
] ]
File added
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-05-23 14:57+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: models.py:260
msgid "Someone requested access to your circle"
msgstr ""
#: models.py:280
msgid "Your access request has been accepted"
msgstr ""
#: models.py:280
msgid "Your access request has been refused"
msgstr ""
#: models.py:282
msgid "Your access request has been"
msgstr ""
#: models.py:305
msgid "Auteur inconnu"
msgstr ""
#: models.py:306
msgid "New Circle access request"
msgstr ""
#: models.py:306
msgid "Circle access request"
msgstr ""
#: templates/accessrequests/access_requested.html:5
msgid "requested access to your circle:"
msgstr ""
#: templates/accessrequests/access_requested.html:11
#: templates/accessrequests/access_resolved.html:11
msgid "Don't respond directly to this email."
msgstr ""
#: templates/accessrequests/access_requested.html:11
#: templates/accessrequests/access_resolved.html:11
msgid "Instead, find the request at"
msgstr ""
#: templates/accessrequests/access_resolved.html:5
msgid "Your request to access the circle"
msgstr ""
#: templates/accessrequests/access_resolved.html:5
msgid "has been"
msgstr ""
#: templates/accessrequests/access_resolved.html:5
msgid "accepted"
msgstr ""
#: templates/accessrequests/access_resolved.html:5
msgid "denied"
msgstr ""
File added
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-05-23 14:57+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: models.py:260
msgid "Someone requested access to your circle"
msgstr "Someone requested access to your circle"
#: models.py:280
msgid "Your access request has been accepted"
msgstr "Your access request has been accepted"
#: models.py:280
msgid "Your access request has been refused"
msgstr "Your access request has been refused"
#: models.py:305
msgid "Auteur inconnu"
msgstr "Unknown author"
#: models.py:306
msgid "New Circle access request"
msgstr "New Circle access request"
#: models.py:306
msgid "Circle access request"
msgstr "Circle access request"
#: templates/accessrequests/access_requested.html:5
msgid "requested access to your circle:"
msgstr "requested access to your circle:"
#: templates/accessrequests/access_requested.html:11
#: templates/accessrequests/access_resolved.html:11
msgid "Don't respond directly to this email."
msgstr "Don't respond directly to this email."
#: templates/accessrequests/access_requested.html:11
#: templates/accessrequests/access_resolved.html:11
msgid "Instead, find the request at"
msgstr "Instead, find the request at"
#: templates/accessrequests/access_resolved.html:5
msgid "Your request to access the circle"
msgstr "Your request to access the circle"
#: templates/accessrequests/access_resolved.html:5
msgid "has been"
msgstr "has been"
#: templates/accessrequests/access_resolved.html:5
msgid "accepted"
msgstr "accepted"
#: templates/accessrequests/access_resolved.html:5
msgid "denied"
msgstr "denied"
File added
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-05-23 14:57+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: models.py:260
msgid "Someone requested access to your circle"
msgstr ""
#: models.py:280
msgid "Your access request has been accepted"
msgstr ""
#: models.py:280
msgid "Your access request has been refused"
msgstr ""
#: models.py:282
msgid "Your access request has been"
msgstr ""
#: models.py:305
msgid "Auteur inconnu"
msgstr ""
#: models.py:306
msgid "New Circle access request"
msgstr ""
#: models.py:306
msgid "Circle access request"
msgstr ""
#: templates/accessrequests/access_requested.html:5
msgid "requested access to your circle:"
msgstr ""
#: templates/accessrequests/access_requested.html:11
#: templates/accessrequests/access_resolved.html:11
msgid "Don't respond directly to this email."
msgstr ""
#: templates/accessrequests/access_requested.html:11
#: templates/accessrequests/access_resolved.html:11
msgid "Instead, find the request at"
msgstr ""
#: templates/accessrequests/access_resolved.html:5
msgid "Your request to access the circle"
msgstr ""
#: templates/accessrequests/access_resolved.html:5
msgid "has been"
msgstr ""
#: templates/accessrequests/access_resolved.html:5
msgid "accepted"
msgstr ""
#: templates/accessrequests/access_resolved.html:5
msgid "denied"
msgstr ""
File added
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-05-23 14:57+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: models.py:260
msgid "Someone requested access to your circle"
msgstr "Quelqu'un a demandé l'accès à votre cercle"
#: models.py:280
msgid "Your access request has been accepted"
msgstr "Votre demande d'accès a été accepté"
#: models.py:280
msgid "Your access request has been refused"
msgstr "Votre demande d'accès a été refusé"
#: models.py:305
msgid "Auteur inconnu"
msgstr "Auteur inconnu"
#: models.py:306
msgid "New Circle access request"
msgstr "Nouvelle demande d'accès à un cercle"
#: models.py:306
msgid "Circle access request"
msgstr "Demande d'accès à un cercle"
#: templates/accessrequests/access_requested.html:5
msgid "requested access to your circle:"
msgstr "a demandé l'accès à votre cercle: "
#: templates/accessrequests/access_requested.html:11
#: templates/accessrequests/access_resolved.html:11
msgid "Don't respond directly to this email."
msgstr "Ne répondez pas directement à cet email."
#: templates/accessrequests/access_requested.html:11
#: templates/accessrequests/access_resolved.html:11
msgid "Instead, find the request at"
msgstr "A la place, vous pouvez retrouver la demande sur"
#: templates/accessrequests/access_resolved.html:5
msgid "Your request to access the circle"
msgstr "Votre demande d'accès au cercle"
#: templates/accessrequests/access_resolved.html:5
msgid "has been"
msgstr "a été"
#: templates/accessrequests/access_resolved.html:5
msgid "accepted"
msgstr "accepté"
#: templates/accessrequests/access_resolved.html:5
msgid "denied"
msgstr "refusé"
...@@ -14,6 +14,7 @@ from django.dispatch import receiver ...@@ -14,6 +14,7 @@ from django.dispatch import receiver
from django.template import loader from django.template import loader
from djangoldp.activities.services import ActivityQueueService from djangoldp.activities.services import ActivityQueueService
from djangoldp.models import Model from djangoldp.models import Model
from django.utils.encoding import force_text
from djangoldp_circle.permissions import CirclePermissions, CircleMemberPermissions, CircleAccessRequestPermissions from djangoldp_circle.permissions import CirclePermissions, CircleMemberPermissions, CircleAccessRequestPermissions
from djangoldp_circle.views import CircleMembersViewset from djangoldp_circle.views import CircleMembersViewset
import logging import logging
...@@ -205,6 +206,9 @@ def delete_circle_on_last_member_delete(sender, instance, **kwargs): ...@@ -205,6 +206,9 @@ def delete_circle_on_last_member_delete(sender, instance, **kwargs):
# if this was the last member of the circle, the circle should be deleted too # if this was the last member of the circle, the circle should be deleted too
if instance.circle.pk is not None and instance.circle.members.count() == 0: if instance.circle.pk is not None and instance.circle.members.count() == 0:
instance.circle.delete() instance.circle.delete()
# delete any CircleAccessRequests which were made on this circle by this user
CircleAccessRequest.objects.filter(circle=instance.circle, user=instance.user).delete()
@receiver(pre_delete, sender=CircleMember) @receiver(pre_delete, sender=CircleMember)
def manage_deleted_owner(sender, instance, **kwargs): def manage_deleted_owner(sender, instance, **kwargs):
...@@ -251,36 +255,36 @@ if 'djangoldp_notification' in settings.DJANGOLDP_PACKAGES: ...@@ -251,36 +255,36 @@ if 'djangoldp_notification' in settings.DJANGOLDP_PACKAGES:
# when an access request is created, send a notification to Circle administrators # when an access request is created, send a notification to Circle administrators
if ar.status == 'Pending': if ar.status == 'Pending':
message_html = render_to_string('accessrequests/access_requested.html', { html_message = render_to_string('accessrequests/access_requested.html', {
"instance": ar, "instance": ar,
'on': on, 'on': on,
}) })
send_mail( send_mail(
"Someone requested access to your circle", _("Someone requested access to your circle"),
to_text(message_html), to_text(html_message),
email_from, email_from,
[instance.user.email], [instance.user.email],
fail_silently = False, fail_silently = False,
html_message = message_html html_message = html_message
) )
# when an access request is resolved, add the user as a CircleMember if approved, # when an access request is resolved, add the user as a CircleMember if approved,
# and send a notification to the user who made the request # and send a notification to the user who made the request
else: else:
html_message = loader.render_to_string( html_message = loader.render_to_string(
'accessrequests/access_resolved.html', 'accessrequests/access_resolved.html',
{ {
'on': on, 'on': on,
'instance': instance, 'instance': ar,
'accepted': ar.status == "Accepted" 'accepted': ar.status == "Accepted"
} }
) )
email_title = (ar.status == "Accepted") if _("Your access request has been accepted") else _("Your access request has been refused")
send_mail( send_mail(
"Your access request has been " + ar.status, email_title,
"", to_text(html_message),
email_from, email_from,
[instance.user.email], [instance.user.email],
fail_silently=(not settings.DEBUG), fail_silently=(not settings.DEBUG),
...@@ -303,27 +307,30 @@ if 'djangoldp_notification' in settings.DJANGOLDP_PACKAGES: ...@@ -303,27 +307,30 @@ if 'djangoldp_notification' in settings.DJANGOLDP_PACKAGES:
notification_type = "CircleAccessRequest" notification_type = "CircleAccessRequest"
author = instance.user if created else instance.responded_to_by author = instance.user if created else instance.responded_to_by
author = author.urlid if author is not None else str(_("Auteur inconnu")) author = author.urlid if author is not None else str(_("Auteur inconnu"))
summary = 'New Circle access request' if created else "Circle access request " + instance.status summary = (instance.status == "Accepted") if _("Your access request has been accepted") else _("Your access request has been refused")
if created:
summary = _('New Circle access request')
notification = { notification = {
"@context": settings.LDP_RDF_CONTEXT, "@context": settings.LDP_RDF_CONTEXT,
"object": notification_obj, "object": notification_obj,
"author": author, "author": author,
"type": notification_type, "type": notification_type,
"summary": summary "summary": force_text(summary)
} }
def discover_circle_access_requests_inbox(target_urlid): def discover_circle_access_requests_inbox(target_urlid):
parsed_url = urlparse(target.urlid) parsed_url = urlparse(target_urlid)
# TODO: proper inbox discovery # TODO: proper inbox discovery
return "{}://{}/circleaccessrequests/inbox/".format(parsed_url.scheme, parsed_url.netloc) distant_server_inbox = "{}://{}/circleaccessrequests/inbox/".format(parsed_url.scheme, parsed_url.netloc)
return distant_server_inbox
def send_notification_to_user(user): def send_notification_to_user(user):
from djangoldp_notification.models import Notification from djangoldp_notification.models import Notification
if Model.is_external(user): if Model.is_external(user):
inbox = discover_circle_access_requests_inbox(target.urlid) inbox = discover_circle_access_requests_inbox(target.user.urlid)
ActivityQueueService.send_activity(inbox, notification) ActivityQueueService.send_activity(inbox, notification)
else: else:
Notification.objects.create(user=user, object=notification_obj['@id'], author=author, type=notification_type, summary=summary) Notification.objects.create(user=user, object=notification_obj['@id'], author=author, type=notification_type, summary=summary)
...@@ -333,8 +340,6 @@ if 'djangoldp_notification' in settings.DJANGOLDP_PACKAGES: ...@@ -333,8 +340,6 @@ if 'djangoldp_notification' in settings.DJANGOLDP_PACKAGES:
if created: if created:
for target in instance.circle.members.filter(is_admin=True): for target in instance.circle.members.filter(is_admin=True):
notification['target'] = target.user.urlid notification['target'] = target.user.urlid
send_notification_to_user(target.user) send_notification_to_user(target.user)
elif instance.status != "Pending": elif instance.status != "Pending":
send_notification_to_user(instance.user) send_notification_to_user(instance.user)
...@@ -27,8 +27,13 @@ class CirclePermissions(LDPPermissions): ...@@ -27,8 +27,13 @@ class CirclePermissions(LDPPermissions):
if obj.status == 'Public': if obj.status == 'Public':
perms = perms.union({'view'}) perms = perms.union({'view'})
# I can join the public circle
if is_authenticated_user(user): if is_authenticated_user(user):
perms = perms.union({'add'}) perms = perms.union({'add'})
# I can view - but not automatically join - restricted circles
elif obj.status == 'Restricted':
perms = perms.union({'view'})
return perms return perms
......
{% load i18n %}
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<div> <div>
{%if instance.user.name is not None %}{{instance.user.name}}{% else %}{{instance.user.urlid}}{% endif %} requested access to your circle {{instance.circle.urlid}} {%if instance.user.name is not None %}{{instance.user.name}}{% else %}{{instance.user.urlid}}{% endif %} {% trans "requested access to your circle:" %} {{instance.circle.name}}
</div> </div>
<div style='margin-top:10px;'> <div style='margin-top:10px;'>
<p style='font-size:small;color:#777'> <p style='font-size:small;color:#777'>
-- --
<br> <br>
Don't respond directly to this email. {% if on %}Instead, find the request at <a href='{{ on }}' target='_blank'>{{ on }}</a>{% endif %}. {% trans "Don't respond directly to this email." %}{% if on %}{%trans "Instead, find the request at" %} <a href='{{ on }}' target='_blank'>{{ on }}</a>{% endif %}.
<br> <br>
</p> </p>
</div> </div>
......
{% load i18n %}
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<div> <div>
Your request to access the circle {{instance.circle.urlid}} has been {% if accepted %}accepted{% else %}denied{% endif %}. {%trans "Your request to access the circle" %} {{instance.circle.name}} {%trans "has been" %} {% if accepted %}{%trans "accepted" %}{% else %}{%trans "denied" %}{% endif %}.
</div> </div>
<div style='margin-top:10px;'> <div style='margin-top:10px;'>
<p style='font-size:small;color:#777'> <p style='font-size:small;color:#777'>
-- --
<br> <br>
Don't respond directly to this email. {% if on %}Instead, find the request at <a href='{{ on }}' target='_blank'>{{ on }}</a>{% endif %}. {% trans "Don't respond directly to this email." %}{% if on %}{%trans "Instead, find the request at" %} <a href='{{ on }}' target='_blank'>{{ on }}</a>{% endif %}.
<br> <br>
</p> </p>
</div> </div>
......
...@@ -511,3 +511,15 @@ class PermissionsTestCase(APITestCase): ...@@ -511,3 +511,15 @@ class PermissionsTestCase(APITestCase):
response = self.client.patch('/circleaccessrequests/{}/'.format(ar.pk), response = self.client.patch('/circleaccessrequests/{}/'.format(ar.pk),
data=json.dumps(payload), content_type='application/ld+json') data=json.dumps(payload), content_type='application/ld+json')
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
def test_access_request_removed_when_user_removed(self):
self.setUpLoggedInUser()
self.setUpCircle('Restrict', self.user)
other_user = get_random_user()
ar = CircleAccessRequest.objects.create(circle=self.circle, user=other_user, status='Accepted')
cm = CircleMember.objects.get(circle=self.circle, user=other_user)
cm.delete()
ar = CircleAccessRequest.objects.filter(pk=ar.pk)
self.assertFalse(ar.exists())
...@@ -56,6 +56,7 @@ class CirclesRequestableViewset(LDPViewSet): ...@@ -56,6 +56,7 @@ class CirclesRequestableViewset(LDPViewSet):
def get_queryset(self): def get_queryset(self):
return super().get_queryset().exclude(members__user=self.request.user.id)\ return super().get_queryset().exclude(members__user=self.request.user.id)\
.exclude(access_requests__user=self.request.user.id)\
.filter(status="Restricted") .filter(status="Restricted")
...@@ -75,6 +76,20 @@ class CirclesJoinableViewset(LDPViewSet): ...@@ -75,6 +76,20 @@ class CirclesJoinableViewset(LDPViewSet):
return super().get_queryset().exclude(members__user=self.request.user.id)\ return super().get_queryset().exclude(members__user=self.request.user.id)\
.filter(status="Public") .filter(status="Public")
class CircleAccessRequestFilteredByUserViewSet(LDPViewSet):
'''Viewset for accessing all AccessRequests linked to me'''
filter_backends = [LocalObjectFilterBackend]
def __init__(self, **kwargs):
from djangoldp_circle.models import CircleAccessRequest
kwargs['model'] = CircleAccessRequest
kwargs['nested_fields'] = CircleAccessRequest.nested.fields()
super().__init__(**kwargs)
def get_queryset(self):
return super().get_queryset().filter(user=self.request.user.id)
class CircleAccessRequestServerNotificationView(APIView): class CircleAccessRequestServerNotificationView(APIView):
''' '''
......