Why DRF is skipping validation? - django

I have a Validator like this: (Yes it's empty and raises Expception)
class AlphanumericValidator(object):
def __init__(self):
pass
def __call__(self, value):
raise serializers.ValidationError(
'Value should contain only letters, numbers, - and _ characters.'
)
And the serializer:
class CampaignSerializer(CreatorModelSerializer):
name = serializers.CharField(max_length=32, validators=[
AlphanumericValidator()
])
class Meta:
model = Campaign
fields = ('id', 'name')
validators = [
UniqueTogetherValidator(
queryset=Campaign.objects.all(),
fields=['account', 'name'],
message='A campaign with this name is already exists.',
),
]
My Payload:
id: 5
name: "aaq???asdas"
My View:
class CampaignDetailView(APIView):
permission_classes = (IsOwner, )
def get_object(self, request, campaign_pk):
campaign = get_object_or_404(Campaign, pk=campaign_pk)
self.check_object_permissions(request, campaign)
return campaign
def put(self, request, campaign_pk):
campaign = self.get_object(request, campaign_pk)
serializer = CampaignSerializer(
campaign,
data=request.data,
context={
'request': request,
}
)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(
serializer.data,
)
Permission IsOwner:
class IsOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return request.user.profile.account == obj.account
Serializer is validating the name successfully.
Where is my mistake?
Edit: I forgot to add end of the CampaignDetailSerializer.put method. I added them.

My wildest guess, is the way you are using the custom validator is meant to be used along with ModelViewSet, to trigger these validators.
If you are doing something custom, try overriding the validate method on the ModelSerializer
like the following
# I guess that CreatorModelSerializer is a sub-class of ModelSerializer.
class CampaignSerializer(CreatorModelSerializer):
def validate(self, attrs):
# Do Validations
if True:
raise serializers.ValidationError(
'ABBBBALIIIZA'
)
# Otherwise return the attributes
return attrs
class Meta:
model = Campaign
fields = ('id', 'name')
validators = [
UniqueTogetherValidator(
queryset=Campaign.objects.all(),
fields=['account', 'name'],
message='A campaign with this name is already exists.',
),
]
The validation should work properly after this change.

Related

Correct way to use DRF and viewsets

I have a model:
class Definition(Model):
definition_id = BigAutoField(primary_key=True)
definition_name = CharField(max_length=50)
is_active, created_by, created_datetime, last_modified_by, last_modified_datetime = default_model_attrs()
class Meta:
db_table = 'definitions'
def __str__(self: Self) -> str:
return self.definition_name
With this serializer:
class DefinitionSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Definition
fields = [
'url',
'definition_id',
'definition_name',
'attributes',
'created_by',
'created_datetime',
'last_modified_by',
'last_modified_datetime',
'is_active',
]
read_only_fields = [
'url',
'definition_id',
'attributes',
'created_by',
'created_datetime',
'last_modified_by',
'last_modified_datetime',
'is_active',
]
I tried making a view set for a couple of custom actions on create:
class DefinitionViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows definitions to be viewed or edited.
"""
queryset = Definition.objects.all().order_by('definition_id')
serializer_class = DefinitionSerializer
permission_classes = [permissions.IsAuthenticated]
def create(self: Self, request: Request, **kwargs: dict[str, Any]) -> Response:
if not is_valid_db_object_name(request.data['definition_name']):
return Response(
{
'message': f"Invalid definition name -> {request.data['definition_name']}",
},
status=status.HTTP_400_BAD_REQUEST)
definition = Definition(
definition_name=request.data['definition_name'],
created_by=request.user,
last_modified_by=request.user
)
serializer = self.serializer_class(definition,
context={'request': request},
partial=True)
definition.save()
return Response(
data=serializer.data,
status=status.HTTP_201_CREATED
)
But this seems overly complicated for validating a field (is_valid_db_object_name is basically a regex) and to add created_by and last_modified_by. Also, trying to figure out how to make definition_name the only possible field that should be passed for creating and editing. Am I on the right path?
Also, how would the edit method look like if I needed to do the same validation? At this point I just want to make sure I am on track here.
I'm using DRF, because I only need an API, no interface.
The recommended track is to handle most of business logic in serializers, and use the viewsets to map view actions to serializers.
For example, a single field validation can be handled in validate_<field> method in serializer:
class DefinitionSerializer(serializers.HyperlinkedModelSerializer):
# Meta class declarations
def validate_definition_name(self, value):
if value is not <condition>:
raise serializers.ValidationError("Invalid definition name")
return value
# Additional fields can be set in 'create' method
def create(self, validated_data):
instance = Definition(
**validated_data,
created_by=self.context['request']['user'],
last_modified_by=self.context['request']['user']
)
return instance
Whith this setup the view can be simplified to:
def create(self: Self, request: Request, **kwargs: dict[str, Any]) -> Response:
serializer = self.serializer_class(data=request.data,
context={'request': request},
partial=True)
serializer.is_valid()
serializer.save()
return Response(
data=serializer.data,
status=status.HTTP_201_CREATED
)

Django REST Framework: Does ModelSerializer have an option to change the fields dynamically by GET or (POST, PUT, DELETE)?

Does ModelSerializer have an option to change the fields dynamically by GET or (POST, PUT, DELETE)?
While GET requires complex fields such as nested serializers, these are not required for (POST, PUT, DELETE).
I think the solution is to use separate serializers for GET and (POST, PUT, DELETE).
But in that case, I'd have to create quite a few useless serializers.
Is there any good solution?
class PlaylistSerializer(serializers.ModelSerializer):
user = UserDetailSerializer(read_only=True)
tracks = serializers.SerializerMethodField()
is_owner = serializers.SerializerMethodField()
is_added = serializers.SerializerMethodField()
is_favorited = serializers.BooleanField()
class Meta:
model = Playlist
fields = (
"pk",
"user",
"title",
"views",
"is_public",
"is_wl",
"created_at",
"updated_at",
"tracks",
"is_owner",
"is_added",
"is_favorited",
)
def get_is_owner(self, obj):
return obj.user == self.context["request"].user
def get_tracks(self, obj):
queryset = obj.track_set
if queryset.exists():
tracks = TrackSerializer(queryset, context=self.context, many=True).data
return tracks
else:
return []
def get_is_added(self, obj):
try:
return obj.is_added
except AttributeError:
return False
class PlaylistUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Playlist
fields = ("title", "is_public")
first you need to create a class and inherit your serializer from this class as below:
from rest_framework import serializers
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""To be used alongside DRF's serializers.ModelSerializer"""
#classmethod
def default_fieldset(cls):
return cls.Meta.fields
def __init__(self, *args, **kwargs):
self.requested_fields = self._extract_fieldset(**kwargs)
# Fields should be popped otherwise next line complains about
unexpected kwarg
kwargs.pop('fields', None)
super().__init__(*args, **kwargs)
self._limit_fields(self.requested_fields)
def _extract_fieldset(self, **kwargs):
requested_fields = kwargs.pop('fields', None)
if requested_fields is not None:
return requested_fields
context = kwargs.pop('context', None)
if context is None:
return None
return context.get('fields')
def _limit_fields(self, allowed_fields=None):
if allowed_fields is None:
to_exclude = set(self.fields.keys()) - set(self.default_fieldset())
else:
to_exclude = set(self.fields.keys()) - set(allowed_fields)
for field_name in to_exclude or []:
self.fields.pop(field_name)
#classmethod
def all_fields_minus(cls, *removed_fields):
return set(cls.Meta.fields) - set(removed_fields)
then your serializer would be something like this:
class PlaylistSerializer(DynamicFieldsModelSerializer):
class Meta:
model = Playlist
fields = ("pk", "user", "title", "views", "is_public",
"is_wl", "created_at", "updated_at", "tracks", "is_owner",
"is_added", "is_favorited",)
#classmethod
def update_serializer(cls):
return ("title", "is_public")
#classmethod
def view_serializer(cls):
return ("title", "is_public", "is_owner", "is_added")
then you will call your serializer as below:
PlaylistSerializer(instance, fields=PlaylistSerializer.update_serializer()).data

Django Rest Framework - How to use "UniqueTogetherValidator" if one of the fields is provided as a URL variable [duplicate]

I want to save a simple model with Django REST Framework. The only requirement is that UserVote.created_by is set automatically within the perform_create() method. This fails with this exception:
{
"created_by": [
"This field is required."
]
}
I guess it is because of the unique_together index.
models.py:
class UserVote(models.Model):
created_by = models.ForeignKey(User, related_name='uservotes')
rating = models.ForeignKey(Rating)
class Meta:
unique_together = ('created_by', 'rating')
serializers.py
class UserVoteSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
created_by = UserSerializer(read_only=True)
class Meta:
model = UserVote
fields = ('id', 'rating', 'created_by')
views.py
class UserVoteViewSet(viewsets.ModelViewSet):
queryset = UserVote.objects.all()
serializer_class = UserVoteSerializer
permission_classes = (IsCreatedByOrReadOnly, )
def perform_create(self, serializer):
serializer.save(created_by=self.request.user)
How can I save my model in DRF without having the user to supply created_by and instead set this field automatically in code?
Thanks in advance!
I had a similar problem and I solved it by explicitly creating and passing a new instance to the serializer. In the UserVoteViewSet you have to substitute perform_create with create:
def create(self, request, *args, **kwargs):
uv = UserVote(created_by=self.request.user)
serializer = self.serializer_class(uv, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I was able to solve this with one-liner in views.py
def create(self, request, *args, **kwargs):
request.data.update({'created_by': request.user.id})
return super(UserVoteViewSet, self).create(request, *args, **kwargs)
Since this view expects user to be authenticated, don't forget to extend permission_classes for rest_framework.permissions.IsAuthenticated
The other weird way you can do is use signals like this
#receiver(pre_save, sender=UserVote)
def intercept_UserVote(sender, instance, *args, **kwargs):
import inspect
for frame_record in inspect.stack():
if frame_record[3]=='get_response':
request = frame_record[0].f_locals['request']
break
else:
request = None
instance.pre_save(request)
Then basically you can define pre_save in your model
def pre_save(self, request):
# do some other stuff
# Although it shouldn't happen but handle the case if request is None
self.created_by = request.user
The advantage of this system is you can use same bit of code for every model. If you need to change anything just change in pre_save(). You can add more stuff as well
Add the following to the ViewSet:
def perform_create(self, serializer):
serializer.save(user=self.request.user)
And the following on the Serializer:
class Meta:
extra_kwargs = {
'user': {
'required': False,
},
}
Below code worked for me.
Even I was facing same error after many experiments found something, so added all fields in serializer.py in class meta, as shown below -
class Emp_UniSerializer( serializers.ModelSerializer ):
class Meta:
model = table
fields = '__all__' # To fetch For All Fields
extra_kwargs = {'std_code': {'required': False},'uni_code': {'required': False},'last_name': {'required': False},'first_name': {'required': False}}
Here, we can update any field which are in "extra_kwargs", it wont show error ["This field is required."]

How to get authenticated user on serializer class for validation

I'm working on a project using django-rest-framework. In my API view, an authenticated user can create other users. But, only five. Then if there are five users registered by one user, I want to send him in the response that hit the limit. Then, I need to get on my serializer the authenticated user but, I can't find a way to pass it from my ModelViewSet to my serializer.
This is my code:
View:
class ChildUserViewSet(viewsets.ModelViewSet):
serializer_class = ChildUserSerializer
queryset = User.objects.all()
authentication_classes = (
TokenAuthentication,
)
permission_classes = (
IsAuthenticated,
)
def perform_create(self, serializer):
account_group = self.request.user.userprofile.get_account_group
mobile_number = serializer.data.get('mobile_number')
password = serializer.data.get('password')
user = serializer.save()
user.set_password(password)
user.save()
# Generate user profile
UserProfile.objects.create(
user=user,
mobile_number=mobile_number,
user_type=CHILD,
related_account_group=account_group,
)
Serializer:
class ChildUserSerializer(serializers.ModelSerializer):
mobile_number = serializers.CharField()
class Meta:
model = User
fields = (
'first_name',
'last_name',
'email',
'password',
'mobile_number',
)
def validate(self, data):
"""
Check that the start is before the stop.
"""
# Get authenticated user for raise hit limit validation
def validate_email(self, value):
if User.objects.filter(email=value):
raise serializers.ValidationError("This field must be unique.")
return value
def create(self, validated_data):
username = generate_unique_username(
u'{0}{1}'.format(
validated_data['first_name'],
validated_data['last_name'],
)
)
user = User(
username=username,
first_name=validated_data['first_name'],
last_name=validated_data['last_name'],
email=validated_data['email'],
)
user.set_password(validated_data['password'])
user.save()
return user
Then, in the def validate(self, data) function of my serializer, I want to get the currently authenticated user.
How can I pass the request.user from my APIView to my serializer?
I found an even easier way of accomplishing this! It turns out that Rest Framework's GenericAPIView base class (from which all of Rest Framework's generic View classes descend) includes a function called get_serializer_context():
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
As you can see, the returned context object contains the same request object that the View receives. This object then gets set when the serializer is initialized:
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
Thus to access the user who made the request, you just need to call self.context['request'].user from within your Serializer's validate_ function:
class TemplateSerializer(serializers.ModelSerializer):
def validate_parent(self, value):
print(self.context['request'].user)
return value
class Meta:
model = Template
And the best part is that you don't have to override anything in your ModelViewSet, they can stay as simple as you want them to:
class TemplateViewSet(viewsets.ModelViewSet):
serializer_class = TemplateSerializer
permission_classes = [IsAdmin]
In your views when you initialize serializer like
serializer = ChildUserSerializer(data=request.DATA,context={'request':request})
,send a context which contains request.Then in Serializers inside function call
request=self.context['request']
Then you can access request.user.
You can pass additional context to your serializer with serializer = ChildUserSerializer(data, context={'request': request}). You can then access the authenticated user via request.user within your serializer validation method.
In djangorestframework > 3.2.4 the rest_framework.generic.GenericAPIView class includes the http request by default in the serializer context.
So inside your serializer you can access it by: self.context['request'] and the user self.context['request'].user
So your ChildUserSerializer will look like:
class ChildUserSerializer(serializers.ModelSerializer):
mobile_number = serializers.CharField()
....
def validate(self, data):
"""
Check that the start is before the stop.
"""
# Get authenticated user for raise hit limit validation
user = self.context['request'].user
# do something with the user here
def validate_email(self, value):
if User.objects.filter(email=value):
raise serializers.ValidationError("This field must be unique.")
return value
...

How to hide some fields in django-admin?

class Book(models.Model):
title = models.CharField(..., null=True)
type = models.CharField(...)
author = models.CharField(...)
I have a simple class in models.py. In admin I would like to hide title of the book (in book details form) when type of the saved book is 1.
How do this in a simplest way?
For Django > 1.8 one can directly set the fields to be excluded in admin:
class PostCodesAdmin(admin.ModelAdmin):
exclude = ('pcname',)
Hidden fields are directly defined in Django's ORM by setting the Field attribute: editable = False
e.g.
class PostCodes(models.Model):
gisid = models.IntegerField(primary_key=True)
pcname = models.CharField(max_length=32, db_index=True, editable=False)
...
However, setting or changing the model's fields directly may not always be possible or advantegous. In principle the following admin.py setup could work, but won't since exclude is an InlineModelAdmin option.
class PostCodesAdmin(admin.ModelAdmin):
exclude = ('pcname',)
....
A solution working at least in Django 1.4 (and likely later version numbers) is:
class PostCodesAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(PostCodesAdmin, self).get_form(request, obj, **kwargs)
del form.base_fields['enable_comments']
return form
For the admin list-view of the items, it suffices to simply leave out fields not required:
e.g.
class PostCodesAdmin(admin.ModelAdmin):
list_display = ('id', 'gisid', 'title', )
You are to create admin.py in your module (probably book)
class BookAdmin(admin.ModelAdmin):
list_display = ("pk", "get_title_or_nothing")
In Book class:
class Book:
...
def get_title_or_nothing(self):
if self.type == WEIRD_TYPE:
return ""
return self.title
UPDATED:
class BookAdmin(admin.ModelAdmin):
list_display = ("pk", "get_title_or_nothing")
def get_form(self, request, obj=None, **kwargs):
if obj.type == "1":
self.exclude = ("title", )
form = super(BookAdmin, self).get_form(request, obj, **kwargs)
return form
I tried to override get_form() function but some mix up errors occur when I switch in different records. I found there is a get_exclude() function we can override.
Use:
class BookAdmin(admin.ModelAdmin):
def get_exclude(self, request, obj=None):
if obj and obj.type == "1":
# When you create new data the obj is None
return ("title", )
return super().get_exclude(request, obj)
class BookAdmin(admin.ModelAdmin):
exclude = ("fieldname",) # hide fields which you want
Apropos #Lorenz #mrts answer
with Django 2.1 I found that exclude does not work if the field is already specified via fields = .
In that case you may use
self.fields.remove('title')
fields will have to be defined as a list [] for this to work
If you want to maintain the value in the form (for example set a value, i.e. user, based on the request) and hide the field, you can change the widget to forms.HiddenInput():
from django import forms
...
def get_form(self, request, obj=None, **kwargs):
"""Set defaults based on request user"""
# update user field with logged user as default
form = super().get_form(request, obj, **kwargs)
form.base_fields["user"].initial = request.user.id
form.base_fields["user"].widget = forms.HiddenInput()
return form
Here is a working example
class BookAdmin(admin.ModelAdmin):
def get_fieldsets(self, request, obj):
if obj is None:
return [
(
None,
{'fields': ('type', 'description',)}
)
]
elif request.user.is_superuser:
return [
(
None,
{'fields': ('type', 'status', 'author', 'store', 'description',)}
)
]
else:
return [
(
None,
{'fields': ('type', 'date', 'author', 'store',)}
)
]