Commit 3df810c3 authored by Jean-Baptiste's avatar Jean-Baptiste

update: fix lot of federated issues

parent 76a88db1
Pipeline #5451 passed with stage
in 1 minute and 26 seconds
......@@ -106,6 +106,8 @@ class Model(models.Model):
@classonlymethod
def resolve(cls, path):
if settings.BASE_URL in path:
path = path[len(settings.BASE_URL):]
container = cls.resolve_container(path)
try:
resolve_id = cls.resolve_id(path)
......
......@@ -4,6 +4,7 @@ from urllib import parse
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AbstractUser
from django.core.exceptions import ImproperlyConfigured
from django.core.exceptions import ValidationError as DjangoValidationError
from django.core.urlresolvers import get_resolver, resolve, get_script_prefix, Resolver404
......@@ -33,6 +34,8 @@ class LDListMixin:
data = data['ldp:contains']
except (TypeError, KeyError):
pass
if len(data) == 0:
return []
if isinstance(data, dict):
data = [data]
if isinstance(data, str) and data.startswith("http"):
......@@ -186,7 +189,7 @@ class JsonLdRelatedField(JsonLdField):
def to_representation(self, value):
try:
if Model.is_external(value):
return {'@id': value.urlid }
return {'@id': value.urlid}
else:
return {'@id': super().to_representation(value)}
except ImproperlyConfigured:
......@@ -465,7 +468,8 @@ class LDPSerializer(HyperlinkedModelSerializer):
return serializer
def to_internal_value(self, data):
user_case = self.Meta.model is get_user_model() and '@id' in data and not data['@id'].startswith(settings.BASE_URL)
user_case = self.Meta.model is get_user_model() and '@id' in data and not data['@id'].startswith(
settings.BASE_URL)
if user_case:
data['username'] = 'external'
ret = super().to_internal_value(data)
......@@ -521,18 +525,7 @@ class LDPSerializer(HyperlinkedModelSerializer):
getattr(instance, field_name).add(related)
def internal_create(self, validated_data, model):
nested_fk_fields_name = list(filter(lambda key: isinstance(validated_data[key], dict), validated_data))
for field_name in nested_fk_fields_name:
field_dict = validated_data[field_name]
field_model = getattr(model, field_name).field.rel.model
slug_field = Model.slug_field(field_model)
if slug_field in field_dict:
kwargs = {slug_field: field_dict[slug_field]}
sub_inst = field_model.objects.get(**kwargs)
else:
sub_inst = self.internal_create(field_dict, field_model)
validated_data[field_name] = sub_inst
validated_data = self.resolve_fk_instances(model, validated_data)
nested_fields = []
nested_list_fields_name = list(filter(lambda key: isinstance(validated_data[key], list), validated_data))
......@@ -564,6 +557,9 @@ class LDPSerializer(HyperlinkedModelSerializer):
return validated_data
def update(self, instance, validated_data):
model = self.Meta.model
validated_data = self.resolve_fk_instances(model, validated_data)
nested_fields = []
nested_fields_name = list(filter(lambda key: isinstance(validated_data[key], list), validated_data))
for field_name in nested_fields_name:
......@@ -577,12 +573,38 @@ class LDPSerializer(HyperlinkedModelSerializer):
else:
setattr(instance, attr, value)
self.save_or_update_nested_list(instance, nested_fields)
instance.save()
return instance
def resolve_fk_instances(self, model, validated_data):
nested_fk_fields_name = list(filter(lambda key: isinstance(validated_data[key], dict), validated_data))
for field_name in nested_fk_fields_name:
field_dict = validated_data[field_name]
try:
field_model = getattr(model, field_name).field.rel.model
except:
# not fk
continue
slug_field = Model.slug_field(field_model)
sub_inst = None
if slug_field in field_dict:
kwargs = {slug_field: field_dict[slug_field]}
sub_inst = field_model.objects.get(**kwargs)
elif 'urlid' in field_dict and settings.BASE_URL in field_dict['urlid']:
model, sub_inst = Model.resolve(field_dict['urlid'])
elif 'urlid' in field_dict and issubclass(field_model, AbstractUser):
kwargs = {'username': field_dict['urlid']}
sub_inst = field_model.objects.get(**kwargs)
elif 'urlid' in field_dict:
kwargs = {'urlid': field_dict['urlid']}
sub_inst = field_model.objects.get(**kwargs)
if sub_inst is None:
sub_inst = self.internal_create(field_dict, field_model)
validated_data[field_name] = sub_inst
return validated_data
def update_dict_value(self, attr, instance, value):
info = model_meta.get_field_info(instance)
slug_field = Model.slug_field(instance)
......@@ -597,8 +619,20 @@ class LDPSerializer(HyperlinkedModelSerializer):
if relation_info.to_many:
value = self.internal_create(validated_data=value, model=relation_info.related_model)
else:
value[instance._meta.fields_map[attr].remote_field.name] = instance
oldObj = getattr(instance, attr, None)
try:
reverse_attr_name = instance._meta.fields_map[attr].remote_field.name
many = False
except:
rel = list(filter(lambda field: field.name == attr, instance._meta.fields))[0].rel
many = rel.one_to_many
reverse_attr_name = rel.related_name
if many:
value[reverse_attr_name] = [instance]
oldObj = rel.model.object.get(id=value['urlid'])
else:
value[reverse_attr_name] = instance
oldObj = getattr(instance, attr, None)
if oldObj is None:
value = self.internal_create(validated_data=value, model=relation_info.related_model)
else:
......@@ -619,6 +653,7 @@ class LDPSerializer(HyperlinkedModelSerializer):
def save_or_update_nested_list(self, instance, nested_fields):
for (field_name, data) in nested_fields:
manager = getattr(instance, field_name)
field_model = manager.model
slug_field = Model.slug_field(manager.model)
try:
item_pk_to_keep = list(map(lambda e: e[slug_field], filter(lambda x: slug_field in x, data)))
......@@ -639,11 +674,19 @@ class LDPSerializer(HyperlinkedModelSerializer):
saved_item = item
elif slug_field in item:
kwargs = {slug_field: item[slug_field]}
try:
old_obj = manager.model.objects.get(**kwargs)
saved_item = self.get_or_create(field_model, item, kwargs)
elif 'urlid' in item and settings.BASE_URL in item['urlid']:
model, old_obj = Model.resolve(item['urlid'])
if old_obj is not None:
saved_item = self.update(instance=old_obj, validated_data=item)
except manager.model.DoesNotExist:
saved_item = self.internal_create(validated_data=item, model=manager.model)
else:
saved_item = self.internal_create(validated_data=item, model=field_model)
elif 'urlid' in item and issubclass(field_model, AbstractUser):
kwargs = {'username': item['urlid']}
saved_item = self.get_or_create(field_model, item, kwargs)
elif 'urlid' in item:
kwargs = {'urlid': item['urlid']}
saved_item = self.get_or_create(field_model, item, kwargs)
else:
rel = getattr(instance._meta.model, field_name).rel
try:
......@@ -657,3 +700,11 @@ class LDPSerializer(HyperlinkedModelSerializer):
if getattr(manager, 'through', None) is not None and manager.through._meta.auto_created:
manager.remove(saved_item)
manager.add(saved_item)
def get_or_create(self, field_model, item, kwargs):
try:
old_obj = field_model.objects.get(**kwargs)
saved_item = self.update(instance=old_obj, validated_data=item)
except field_model.DoesNotExist:
saved_item = self.internal_create(validated_data=item, model=field_model)
return saved_item
import json
from django.contrib.auth.models import User
from django.test import TestCase
from rest_framework.test import APIRequestFactory, APIClient
......
......@@ -272,31 +272,7 @@ class Update(TestCase):
self.assertEquals(response.data['content'], "post content")
self.assertIn('location', response._headers)
def test_create_sub_object_in_existing_object_with_reverse_1to1_relation(self):
"""
Doesn't work with depth = 0 on UserProfile Model. Should it be ?
"""
user = User.objects.create(username="alex", password="test")
body = [
{
'@id': "_:b975",
'http://happy-dev.fr/owl/#description': "user description",
'http://happy-dev.fr/owl/#dummy': {
'@id': './'
}
},
{
'@id': '/users/{}/'.format(user.pk),
"http://happy-dev.fr/owl/#first_name": "Alexandre",
"http://happy-dev.fr/owl/#last_name": "Bourlier",
"http://happy-dev.fr/owl/#username": "alex",
'http://happy-dev.fr/owl/#userprofile': {'@id': "_:b975"}
}
]
response = self.client.put('/users/{}/'.format(user.pk), data=json.dumps(body),
content_type='application/ld+json')
self.assertEqual(response.status_code, 200)
self.assertIn('userprofile', response.data)
def test_create_sub_object_in_existing_object_with_existing_reverse_1to1_relation(self):
user = User.objects.create(username="alex", password="test")
......@@ -396,23 +372,6 @@ class Update(TestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['peer_user'], None)
def test_m2m_new_link(self):
resource = Resource.objects.create()
job = JobOffer.objects.create(title="first title", slug="job")
body = {
'http://happy-dev.fr/owl/#joboffers': {
'@id': 'http://testserver.com/job-offers/{}/'.format(job.slug),
}
}
response = self.client.put('/resources/{}/'.format(resource.pk),
data=json.dumps(body),
content_type='application/ld+json')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['joboffers']['ldp:contains'][0]['@id'],
"http://testserver.com/job-offers/{}/".format(job.slug))
self.assertEqual(response.data['joboffers']['ldp:contains'][0]['title'], "first title")
def test_m2m_new_link_bis(self):
resource = Resource.objects.create()
job = JobOffer.objects.create(title="first title", slug="job")
......@@ -493,6 +452,62 @@ class Update(TestCase):
self.assertEqual(response.data['joboffers']['ldp:contains'][0]['@id'],
"http://external.job/job/1")
def test_m2m_new_link_external(self):
resource = Resource.objects.create()
body = {
'http://happy-dev.fr/owl/#joboffers': {
'@id': 'http://testserver.com/job-offers/stuff/',
}
}
response = self.client.put('/resources/{}/'.format(resource.pk),
data=json.dumps(body),
content_type='application/ld+json')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['joboffers']['ldp:contains'][0]['@id'],
"http://testserver.com/job-offers/stuff/")
def test_m2m_new_link_local(self):
resource = Resource.objects.create()
job = JobOffer.objects.create(title="first title", slug="job")
body = {
'http://happy-dev.fr/owl/#joboffers': {
'@id': 'http://happy-dev.fr/job-offers/{}/'.format(job.slug),
}
}
response = self.client.put('/resources/{}/'.format(resource.pk),
data=json.dumps(body),
content_type='application/ld+json')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['joboffers']['ldp:contains'][0]['@id'],
"http://happy-dev.fr/job-offers/{}/".format(job.slug))
self.assertEqual(response.data['joboffers']['ldp:contains'][0]['title'], "first title")
def test_update_with_new_fk_relation(self):
user = User.objects.create(username="alex", password="test")
conversation = Conversation.objects.create(author_user=user,
description="conversation description")
body = [
{
'@id': "/conversations/{}/".format(conversation.pk),
'http://happy-dev.fr/owl/#description': "conversation update",
'http://happy-dev.fr/owl/#peer_user': {
'@id': 'http://happy-dev.fr/users/{}'.format(user.pk),
}
}
]
response = self.client.put('/conversations/{}/'.format(conversation.pk), data=json.dumps(body),
content_type='application/ld+json')
self.assertEqual(response.status_code, 200)
self.assertIn('peer_user', response.data)
conversation = Conversation.objects.get(pk=conversation.pk)
self.assertIsNotNone(conversation.peer_user)
user = User.objects.get(pk=user.pk)
self.assertEqual(user.peers_conv.count(), 1)
def test_m2m_user_link_federated(self):
circle = Circle.objects.create(description="cicle name")
body = {
......@@ -508,3 +523,74 @@ class Update(TestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['team']['ldp:contains'][0]['@id'],
"http://external.user/user/1")
def test_m2m_user_link_existing_external(self):
circle = Circle.objects.create(description="cicle name")
ext_user = User.objects.create(username='http://external.user/user/1')
body = {
'http://happy-dev.fr/owl/#description': 'circle name',
'http://happy-dev.fr/owl/#team': {
'http://happy-dev.fr/owl/#@id': ext_user.username,
}
}
response = self.client.put('/circles/{}/'.format(circle.pk),
data=json.dumps(body),
content_type='application/ld+json')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['team']['ldp:contains'][0]['@id'],
ext_user.username)
circle = Circle.objects.get(pk=circle.pk)
self.assertEqual(circle.team.count(), 1)
user = User.objects.get(pk=ext_user.pk)
self.assertEqual(user.circle_set.count(), 1)
def test_create_sub_object_in_existing_object_with_reverse_1to1_relation(self):
"""
Doesn't work with depth = 0 on UserProfile Model. Should it be ?
"""
user = User.objects.create(username="alex", password="test")
body = [
{
'@id': "_:b975",
'http://happy-dev.fr/owl/#description': "user description",
'http://happy-dev.fr/owl/#dummy': {
'@id': './'
}
},
{
'@id': '/users/{}/'.format(user.pk),
"http://happy-dev.fr/owl/#first_name": "Alexandre",
"http://happy-dev.fr/owl/#last_name": "Bourlier",
"http://happy-dev.fr/owl/#username": "alex",
'http://happy-dev.fr/owl/#userprofile': {'@id': "_:b975"}
}
]
response = self.client.put('/users/{}/'.format(user.pk), data=json.dumps(body),
content_type='application/ld+json')
self.assertEqual(response.status_code, 200)
self.assertIn('userprofile', response.data)
def test_m2m_user_link_remove_existing_link(self):
ext_user = User.objects.create(username='http://external.user/user/1')
circle = Circle.objects.create(description="cicle name")
circle.team.add(ext_user)
circle.save()
body = {
'http://happy-dev.fr/owl/#description': 'circle name',
'http://happy-dev.fr/owl/#team': {
}
}
response = self.client.put('/circles/{}/'.format(circle.pk),
data=json.dumps(body),
content_type='application/ld+json')
self.assertEqual(response.status_code, 200)
circle = Circle.objects.get(pk=circle.pk)
self.assertEqual(circle.team.count(), 0)
user = User.objects.get(pk=ext_user.pk)
self.assertEqual(user.circle_set.count(), 0)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment