Following the tutorial we have a serializer and view:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'groups')
class UserList(generics.ListCreateAPIView):
"""
API endpoint that represents a list of users.
"""
model = User
serializer_class = UserSerializer
We'd like to allow the creation of a User with a password field but (obviously) not reveal this over the API.
1. (How) can we allow the extra filed on POST (or do we need separate Serializers/Views)?
2. Do we need to override the post method to apply the correct salt/hashing?
> 1. (How) can we allow the extra filed on POST (or do we need separate
Serializers/Views)?
For this use case you probably want to write the APIView yourself, without relying on the Generic Views. All of the Generic views use the same serializer for validating the input, and serializing the output, which is fine in most cases, but doesn't quite work here.
In your custom APIView, you'll probably want one serializer (which does include a password field) that you use for validating the input, and then a second for actually serializing the user (which doesn't).
> 2. Do we need to override the post method to apply the correct
salt/hashing?
Because creating users is a little different to the usual case of creating a model instance due to the password saving, you probably want to look at overriding the 'restore_object` method on the serializer
def restore_object(self, attrs, instance=None): if instance: #Update user = instance user.username = attrs['username']
user.email = attrs['email'] else:
user = User(username=attrs['username'], email=attrs['email'] , is_staff=False, is_active=True, is_superuser=False) user.set_password(password) user.save() return user
Hope that helps. If you get it working okay, I'd really like to see what you end up with as I think this one is probably a reasonable common case for folks to bump into.
On Friday, 16 November 2012 16:09:00 UTC, Carlton Gibson wrote:
> Hi All,
> Following the tutorial we have a serializer and view:
> class UserSerializer(serializers.HyperlinkedModelSerializer): > class Meta: > model = User > fields = ('url', 'username', 'email', 'groups')
> class UserList(generics.ListCreateAPIView): > """ > API endpoint that represents a list of users. > """ > model = User > serializer_class = UserSerializer
> We'd like to allow the creation of a User with a password field but > (obviously) not reveal this over the API.
> 1. (How) can we allow the extra filed on POST (or do we need separate > Serializers/Views)? > 2. Do we need to override the post method to apply the correct > salt/hashing?
Thanks for the quick reply — that gives me lots to play with over the weekend. :-)
On 16 Nov 2012, at 18:42, Tom Christie <christie....@gmail.com> wrote:
> I'd suggest taking a dig around the existing create/update code as it's actually very simple.
I've been digging around quite a lot — it all seems quite simple really — which is testament to how well thought through it is. (Thank you!)
> If you get it working okay, I'd really like to see what you end up with as I think this one is probably a reasonable common case for folks to bump into.
I'll come back with my solution. (The least I can do.)
> Thanks for the quick reply — that gives me lots to play with over the weekend. :-)
> On 16 Nov 2012, at 18:42, Tom Christie <christie....@gmail.com> wrote:
>> I'd suggest taking a dig around the existing create/update code as it's actually very simple.
> I've been digging around quite a lot — it all seems quite simple really — which is testament to how well thought through it is. > (Thank you!)
>> If you get it working okay, I'd really like to see what you end up with as I think this one is probably a reasonable common case for folks to bump into.
> I'll come back with my solution. > (The least I can do.)
On 16 Nov 2012, at 18:42, Tom Christie <christie....@gmail.com> wrote:
> > 1. (How) can we allow the extra filed on POST (or do we need separate Serializers/Views)?
> For this use case you probably want to write the APIView yourself, without relying on the Generic Views.
> All of the Generic views use the same serializer for validating the input, and serializing the output, which is fine in most cases, but doesn't quite work here.
> In your custom APIView, you'll probably want one serializer (which does include a password field) that you use for validating the input, and then a second for actually serializing the user (which doesn't).
> I'd suggest taking a dig around the existing create/update code as it's actually very simple.
Just following up on this: I haven't written it yet but it is on the list. In the meantime I think I have the opposite problem.
I have a model with an "updated" field which is:
updated = models.DateTimeField(auto_now=True)
DRF is making me provide a (valid) value even though it will never be used.
Here I want "Fewer Fields on POST".
_And_ I can see this sort of thing coming up all the time.
(Just another case, I have a list endpoint that I want users to POST to but on admins GET.) It seems to me it would be good to be able to tweak the serializers somehow to still be able to use the generic views.
(Am on it but am hoping to fire-up more/better brains than merely mine.)
On Thursday, 22 November 2012 14:00:53 UTC+1, Tom Christie wrote:
> It sounds like you're looking for the read_only=True argument unless I'm > missing something.
No, I want the model to be writable via the endpoint — I just don't want the `updated` field to be required (it's value is disregarded anyway). A slight aside: this is the behaviour you get in the Admin, i.e. the updated field wouldn't be shown.
The general problem seems to be needing different serializers — different field lists — for different methods (plus, slightly different, perhaps allowing differences depending on request.user too).
Your answer of writing the view by hand will no doubt work — and I need to do it that way several times to grok everything — but I can see this kind of thing coming up quite a lot. It would be nice to handle it with the generic views.
(Perhaps we already can — but your original suggestion seemed to imply not.)
Anyhow, I just wanted to out the thought out there — mainly in case there's something obvious I've missed. I'm working on it currently so I will come back when I'm the other side of something that runs.
On Thursday, 22 November 2012 14:00:53 UTC+1, Tom Christie wrote:
> It sounds like you're looking for the read_only=True argument unless I'm missing something.
Just wanted to share how I went about tackling this. I have a similar need, working with a model that has a `password_hash` field and accepts a `password` through the API. I ended up using the restore_object and convert_object methods of ModelSerializer to get it done and still rely on the generic views. I was hoping to do it in a way that doesn't lose a lot of the work that ModelSerializer does for you. Does this look like a decent implementation?
*models.py*
class Account(models.Model):
email = models.EmailField(unique=True, db_index=True, max_length=255)
password_hash = models.CharField(max_length=255)
*serializers.py*
class AccountSerializer(serializers.ModelSerializer):
password = serializers.CharField(widget=forms.PasswordInput())
password_hash = serializers.CharField(read_only=True)
created = serializers.DateTimeField(read_only=True)
updated = serializers.DateTimeField(read_only=True)
class Meta:
model = Account
def convert_object(self, obj):
"""Remove password field when serializing an object"""
del self.fields['password']
def restore_object(self, attrs, instance=None):
"""Hash password field and remove it when deserializing"""
attrs['password_hash'] = make_password(attrs['password'])
del attrs['password']
> I've been digging around quite a lot — it all seems quite simple really — > which is testament to how well thought through it is. > (Thank you!)
> If you get it working okay, I'd really like to see what you end up with > as I think this one is probably a reasonable common case for folks to bump > into.
> I'll come back with my solution. > (The least I can do.)
Sorry for the slow reply; I quickly went with the "second endpoint" approach which delayed taking on the real problem.
On Sunday, 2 December 2012 00:50:14 UTC+1, Rod Afshar wrote:
> Just wanted to share how I went about tackling this.
I've bashed a working solution together combining Tom and Rod's approach.
I've added a ?set_password query parameter to the UserList view that switches the serializer on POSTs:
class UserList(generics.ListCreateAPIView):
"""
API endpoint that represents a list of users.
"""
permission_classes = (permissions.IsAdminUserOrPostOnly,)
model = User
serializer_class = UserSerializer
def restore_object(self, attrs, instance=None):
if instance: #Update
user = instance
user.username = attrs['username']
user.email = attrs['email']
else:
user = User(username=attrs['username'], email=attrs['email'],
is_staff=False, is_active=True, is_superuser=False)
user.set_password(attrs['password'])
# note I don't save() here. The view's create() does that.
return user
As I say, it's all work in progress (and all entirely derivative) but it works with auth.User and (thus far) has the desired behaviour. I'll continue working on it.
I'm still trying to find the best way to tackle this topic without creating separate serializers or endpoints. I tried Rod's way, which seems like a clean way in doing it. However, I get an exception when deleting the field from the serializer in convert_object.
def convert_object(self, obj):
"""Remove password field when serializing an object"""
del self.fields['password']
Exception Type:KeyErrorException Value:
'password'
Exception Location:/home/bas/.virtualenv/backupcp-ws/lib/python2.7/site-packages/djan go/utils/datastructures.py in __delitem__, line 137
On Saturday, December 1, 2012 3:50:14 PM UTC-8, Rod Afshar wrote:
> Just wanted to share how I went about tackling this. I have a similar > need, working with a model that has a `password_hash` field and accepts a > `password` through the API. I ended up using the restore_object and > convert_object methods of ModelSerializer to get it done and still rely on > the generic views. I was hoping to do it in a way that doesn't lose a lot > of the work that ModelSerializer does for you. Does this look like a decent > implementation?
> def restore_object(self, attrs, instance=None):
> """Hash password field and remove it when deserializing"""
> attrs['password_hash'] = make_password(attrs['password'])
> del attrs['password']
>> I've been digging around quite a lot — it all seems quite simple really — >> which is testament to how well thought through it is. >> (Thank you!)
>> If you get it working okay, I'd really like to see what you end up with >> as I think this one is probably a reasonable common case for folks to bump >> into.
>> I'll come back with my solution. >> (The least I can do.)
On Sunday, 27 January 2013 03:49:33 UTC+1, Otto Yiu wrote:
> I'm still trying to find the best way to tackle this topic without > creating separate serializers or endpoints. I tried Rod's way, which seems > like a clean way in doing it. However, I get an exception when deleting the > field from the serializer in convert_object.
> def convert_object(self, obj):
> """Remove password field when serializing an object"""
> del self.fields['password']
> Exception Type:KeyErrorException Value:
> 'password'
> Exception Location:/home/bas/.virtualenv/backupcp-ws/lib/python2.7/site-packages/djan go/utils/datastructures.py > in __delitem__, line 137
> On Saturday, December 1, 2012 3:50:14 PM UTC-8, Rod Afshar wrote:
>> Just wanted to share how I went about tackling this. I have a similar >> need, working with a model that has a `password_hash` field and accepts a >> `password` through the API. I ended up using the restore_object and >> convert_object methods of ModelSerializer to get it done and still rely on >> the generic views. I was hoping to do it in a way that doesn't lose a lot >> of the work that ModelSerializer does for you. Does this look like a decent >> implementation?
>> def restore_object(self, attrs, instance=None):
>> """Hash password field and remove it when deserializing"""
>> attrs['password_hash'] = make_password(attrs['password'])
>> del attrs['password']
>>> I've been digging around quite a lot — it all seems quite simple really >>> — which is testament to how well thought through it is. >>> (Thank you!)
>>> If you get it working okay, I'd really like to see what you end up >>> with as I think this one is probably a reasonable common case for folks to >>> bump into.
>>> I'll come back with my solution. >>> (The least I can do.)
> HIDDEN_PASSWORD_STRING = '<hidden>' > class PasswordField(serializers.CharField): > """Special field to update a password field.""" > widget = forms.widgets.PasswordInput
> def from_native(self, value): > """Hash if new value sent, else retrieve current password""" > from django.contrib.auth.hashers import make_password > if value == HIDDEN_PASSWORD_STRING or value == '': > return self.parent.object.password > else: > return make_password(value)
> def to_native(self, value): > """Hide hashed-password in API display""" > return HIDDEN_PASSWORD_STRING
On Friday, November 16, 2012 5:09:00 PM UTC+1, Carlton Gibson wrote:
> Hi All,
> Following the tutorial we have a serializer and view:
> class UserSerializer(serializers.HyperlinkedModelSerializer): > class Meta: > model = User > fields = ('url', 'username', 'email', 'groups')
> class UserList(generics.ListCreateAPIView): > """ > API endpoint that represents a list of users. > """ > model = User > serializer_class = UserSerializer
> We'd like to allow the creation of a User with a password field but > (obviously) not reveal this over the API.
> 1. (How) can we allow the extra filed on POST (or do we need separate > Serializers/Views)? > 2. Do we need to override the post method to apply the correct > salt/hashing?
On Monday, March 18, 2013 12:06:20 PM UTC-5, Neamar Tucote wrote:
> Hi,
> Had the same problem, solved it by creating a special Field for password. > Hope this helps
>> HIDDEN_PASSWORD_STRING = '<hidden>'
>> class PasswordField(serializers.CharField): >> """Special field to update a password field.""" >> widget = forms.widgets.PasswordInput
>> def from_native(self, value): >> """Hash if new value sent, else retrieve current password""" >> from django.contrib.auth.hashers import make_password >> if value == HIDDEN_PASSWORD_STRING or value == '': >> return self.parent.object.password >> else: >> return make_password(value)
>> def to_native(self, value): >> """Hide hashed-password in API display""" >> return HIDDEN_PASSWORD_STRING
> On Friday, November 16, 2012 5:09:00 PM UTC+1, Carlton Gibson wrote:
>> Hi All,
>> Following the tutorial we have a serializer and view:
>> class UserSerializer(serializers.HyperlinkedModelSerializer): >> class Meta: >> model = User >> fields = ('url', 'username', 'email', 'groups')
>> class UserList(generics.ListCreateAPIView): >> """ >> API endpoint that represents a list of users. >> """ >> model = User >> serializer_class = UserSerializer
>> We'd like to allow the creation of a User with a password field but >> (obviously) not reveal this over the API.
>> 1. (How) can we allow the extra filed on POST (or do we need separate >> Serializers/Views)? >> 2. Do we need to override the post method to apply the correct >> salt/hashing?