Reputation: 1469
When i'm creating a new resource with a foreign relation, specified as, i.e., {"pk": 20}
, i get a new unwanted FK-item created.
I have Order
model class with a relations to the Language
model, so when creating an Order instance, i may have to specify the order's languages. Language list should be constant, and the users must not have an ability to modify existant or to create the new languages.
Order
resource:
class OrderResource(ModelResource):
user = fields.ForeignKey(UserResource, 'user', null=True, full=True)
src_lang = fields.ForeignKey(LanguageResource, 'src_lang', null=True, full=True)
dst_lang = fields.ForeignKey(LanguageResource, 'dst_lang', null=True, full=True)
def obj_create(self, bundle, request=None, **kwargs):
return super(OrderResource, self).obj_create(bundle, request, user=request.user)
class Meta:
resource_name = 'orders'
queryset = Order.objects.all()
serializer = Serializer(['json'])
authentication = MultiAuthentication(SessionAuthentication(), ApiKeyAuthentication())
authorization = ResourceAuthorization()
And here is a Language
resource:
class Language(models.Model):
name = models.CharField(max_length=100)
code = models.CharField(max_length=100)
class LanguageResource(ModelResource):
class Meta:
resource_name = 'languages'
queryset = Language.objects.all()
allowed_methods = ['get']
authorization = ReadOnlyAuthorization()
serializer = Serializer(['json'])
I'm trying to create a new Order
with jQuery:
var data = JSON.stringify({
"comment": "Something random",
"src_lang": {"pk": "20"},
"dst_lang": "/api/v2/languages/72/"
});
$.ajax({
type: 'POST',
url: '/api/v2/orders/',
data: data,
dataType: "json",
contentType: "application/json"
});
Instead of setting the pk:20
to src_lang_id
field, it creates a new Language
with empty fields for src_lang
and sets a correct value for dst_lang
. But empty fields are restricted with the Language
model definition. How it saves it?
Also it's enough strange because i've straightly specified readonly access for language model, and only get
method for accessing the supported language list.
If i declare language fields of OrderResource
class as, i.e.: src_lang = fields.ForeignKey(LanguageResource, 'src_lang', null=True, full=True, readonly=True)
, it creates nothing, but also does not set any values for the foreign keys.
So, i just need to specify an existant language, i don't need to create it.
ResourceAuthorization
:
class ResourceAuthorization(Authorization):
def is_authorized(self, request, object=None):
user = getattr(request, 'user', None)
if not user:
return False
return user.is_authenticated()
def apply_limits(self, request, object_list):
if request and hasattr(request, 'user'):
if request.user.is_superuser:
return object_list
return object_list.filter(user=request.user)
return object_list.none()
I found nothing more clever making fields read only and overriding obj_create
method:
class OrderResource(ModelResource):
user = fields.ForeignKey(UserResource, 'user', null=True, full=True)
src_lang = fields.ForeignKey(LanguageResource, 'src_lang', null=True, full=True, blank=True, readonly=True)
dst_lang = fields.ForeignKey(LanguageResource, 'dst_lang', null=True, full=True, blank=True, readonly=True)
def obj_create(self, bundle, request=None, **kwargs):
src_lang_id, dst_lang_id = bundle.data.get('src_lang', None), bundle.data.get('dst_lang', None)
if not all([src_lang_id, dst_lang_id]):
raise BadRequest('You should specify both source and destination language codes')
src_lang, dst_lang = Language.objects.guess(src_lang_id), Language.objects.guess(dst_lang_id)
if not all([src_lang, dst_lang]):
raise BadRequest('You should specify both source and destination language codes')
return super(OrderResource, self).obj_create(
bundle, request, user=request.user, src_lang=src_lang, dst_lang=dst_lang
)
class Meta:
resource_name = 'orders'
queryset = Order.objects.all()
serializer = Serializer(['json'])
authentication = MultiAuthentication(SessionAuthentication(), ApiKeyAuthentication())
authorization = ResourceAuthorization()
Upvotes: 1
Views: 693
Reputation: 15776
As outlined in this answer to your question, src_lang
should correspond to a resource, not to some other value. I suspect that when the POST occurs and it doesn't find a resource pk=20
, it creates a new Language
object and calls save
without Django model validation, allowing a blank field to exist in the created Language
.
One way of forcing a read-only type resource is to create a resource which doesn't allow obj_create
.
class ReadOnlyLanguageResource(ModelResource):
# All the meta stuff here.
def obj_create(self):
# This should probably raise some kind of http error exception relating
# to permission denied rather than Exception.
raise Exception("Permission denied, cannot create new language resource")
This resource is then referenced from your Order
resource, overriding just the src_lang
field to point to your read only resource.
class OrderResource(ModelResource):
user = fields.ForeignKey(UserResource, 'user', null=True, full=True)
src_lang = fields.ForeignKey(ReadOnlyLanguageResource, 'src_lang')
dst_lang = fields.ForeignKey(ReadOnlyLanguageResource, 'dst_lang')
Any request that references an existing resource will complete as per normal (but you'll need to reference the resource correctly, not using pk=20
). Any request the references an unknown language will fail as a new Language
object cannot be created.
Upvotes: 2
Reputation: 9568
You should specify the src_lang
in the format /api/v2/languages/72/
corresponding the pk=20
.
Secondly what exactly is ResourceAuthorization
? The documentation lists ReadOnlyAuthorization
which might be useful to you.
Also the authorization applies the resource, not the underlying model. When creating a new object for fk
, it does not use the REST Api but django.db.models
and its permissions. So the authorizations might not apply via the foreign key constraint.
Upvotes: 1