How do you resolve unique field clashes in django? - python

I have a model that looks like this:
class Foo(models.Model):
unique_field = models.URLField(unique=True)
another_field = models.TextField()
# etc...
and its corresponding ViewSet looks like this (bound to /foo/):
class FooViewSet(
viewsets.GenericViewSet,
mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.DestroyModelMixin,
):
queryset = Foo.objects.all()
serializer_class = FooSerializer
When I make a POST request to /foo/, if there's no clash I just want the default behaviour (create a new row in the database and return its serialized form as a json blob).
The problem is that when there is a clash the server just throws an error. What I'd rather have it do is return the json blob for the existing data, rather than crashing. Or at the very least return some kind of descriptive error.
Is there a way to get this behaviour without manually overriding create()?

Related

I receive typerror when use request method in drf

With some help, I solved this issue.
My api is work, but today I found this error when I try to access '/api/v1/docs'
AttributeError at /api/v1/docs/
'NoneType' object has no attribute 'method'
I know that the error is here:
def get_fields(self):
fields = super().get_fields()
if self.context['request'].method in ['POST', 'PATCH', 'PUT']:
fields['products'] = serializers.ListField(
write_only=True,
child=serializers.IntegerField()
)
return fields
When I remove .method, the access to the /api/v1/docs/ works, but my solution to post some products in bundleproducts, doesn't work.
My code:
view.py
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
model = Product
class BundleProductViewSet(viewsets.ModelViewSet):
queryset = BundleProduct.objects.all()
serializer_class = BundleProductSerializer
model = BundleProduct
class BundleViewSet(viewsets.ModelViewSet):
queryset = Bundle.objects.all()
serializer_class = BundleSerializer
model = Bundle
This is probably caused by this serializer being used as a nested serializer in another serializer. So lets say the definition for the serializer in question is:
class MySerializer(serializers.Serializer):
...
And you have another serializer like this:
class OtherSerializer(serializers.Serializer):
my_field = MySerializer()
In this case, when instantiating an instance of OtherSerializer, its context is not passed automatically to MySerializer, so there would not be a request in the context of MySerializer. You can either add the context to nested serializer manually, or in the get_fields method, check that request exists in self.context and proceed accordingly.
Also, I am not sure what you are trying to accomplish, but if you provide a field with
write_only=True
in serializer class definition, the field would not be present when reading the serializer, i.e for get requests in general, which seems like what you are trying to do here. So adding the products field as write_only would have the same effect, you do not need to override get_fields method

In a Django serializer, how to set foreign key field based on view argument?

Using the Django REST Framework, I would like to allow users to create and save instances of a Django model through a ListCreateAPIView (via POST). One of the fields (a foreign-key field called domain) shall be determined from a view parameter as defined in urls.py.
Furthermore, the user can modify the model instance later using PUT or PATCH requests to a RetrieveUpdateDestroyAPIView endpoint (using the same serializer). I don't want the user to be able to modify the domain field at this point.
While I have the code for the model and the view / serializer structure ready, I'm not sure how to tell the serializer to determine the value of the domain field based on the view parameter. Here's what I got:
class RRset(models.Model):
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(null=True)
domain = models.ForeignKey(Domain, on_delete=models.CASCADE, related_name='rrsets')
subname = models.CharField(max_length=255, blank=True)
type = models.CharField(max_length=10)
... and a straight-forward ListCreateAPIView:
class RRsetsDetail(generics.ListCreateAPIView):
serializer_class = RRsetSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
name = self.kwargs['name']
return RRset.objects.filter(domain__name=name, domain__owner=self.request.user.pk)
urls.py contains the following line:
url(r'^domains/(?P<name>[a-zA-Z\.\-_0-9]+)/rrsets/$', RRsetsDetail.as_view(), name='rrsets')
This allows the user to list and create RRset objects using the RRsetsSerializer serializer (the name field is listed for completeness only, but I do not believe it to be important in this context):
class RRsetSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField()
def get_name(self, obj):
return '.'.join(filter(None, [obj.subname, obj.domain.name])) + '.' # returns 'subname.name.'
class Meta:
model = RRset
fields = ('created', 'updated', 'domain', 'name', 'type',)
read_only_fields = ('created', 'updated', 'domain', 'type',)
Questions:
What do I need to modify to have the serializer take the domain name from the view name parameter?
The the serializer's read_only_fields setting prevents the user from modifying the domain field later. However, I'm not sure if this setting somehow interacts with the serializer trying to set a default value (can the serializer write the default value, even if read-only is set)?
To summarize: What I'm looking for is something like a "write-once field with a default value based on a view parameter".
I think you are looking for a HiddenField with a combination of CreateOnlyDefault
HiddenField
A field class that does not take a value based on user input, but instead takes its value from a default value or callable.
CreateOnlyDefault
A default class that can be used to only set a default argument during
create operations. During updates the field is omitted.
It takes a single argument, which is the default value or callable
that should be used during create operations.
And because you want to access the view, you can't just use callable, but you have to use Class-based callable which can have access to a context data.
class DomainDefault(object):
def set_context(self, serializer_field):
view = serializer_field.context['view']
request = serializer_field.context['request']
self.domain = ...#determine the domain based on request+view
def __call__(self):
return self.domain
class RRsetSerializer(serializers.ModelSerializer):
domain = serializers.HiddenField(default=serializers.CreateOnlyDefault(DomainDefault()))

django rest framework: Get url parameter value in a model property

I have a Product model and one propery in it is "my_test_fn". This is called from my serializer. My requirement is, I want to do some calculations based on the filter passing through the url. How can I get the url parameter values in a model property?
I want to get "filters" value in my_test_fn
models.py
class Product(AbstractProduct):
product_id = models.UUIDField(default=uuid.uuid4, editable=False)
##more fields to go
def my_test_fn(self):
filters = self.request.query_params.get('filters', None)
return {"key":"value"}
serializer.py
class MySerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ('id','product_id','sku', 'title', 'my_test_fn',)
views.py
class ProductDetailConfiguration(viewsets.ViewSet):
lookup_field = 'product_id'
def retrieve(self, request, product_id=None):
queryset = Product.objects.filter(product_id=product_id)[0]
serializer = ProductConfigurationCustomSerializer(queryset, context={'request': request})
return Response(serializer.data)
API url:
http://127.0.0.1:8000/api/v1/product-configuration/2FC2AA43-07F5-DCF4-9A74-C840FDD8280A?filters=5
This logic belongs in the serializer, not the model. You can access it there via self.context['request'].
I guess what you want is not possible (have the my_fn on the model itself).
You would need to use a SerializerMethodField, so you will have access to the object, but to the request (and the various parameters of it) as well.

different validation in drf serializer per request method

Lets say i have a model like so:
class MyModel(models.Model):
first_field = models.CharField()
second_field = models.CharField()
and an API view like so:
class MyModelDetailAPI(GenericAPIView):
serializer_class = MyModelSerializer
def patch(self, request, *args, **kwargs):
# Do the update
def post(self, request, *args, **kwargs):
# Do the post
The first_field is a field that is only inserted in the POST method (and is mandatory) but on each update, the user can't change its value so the field in the PATCH method is not mandatory.
How can i write my serializer so that the first_field is required on POST but not required on PATCH. Is there any way of dynamically setting the required field so i can still use the DRF validation mechanism? Some sort of validator dispatcher per request method?
I want something like this for example:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = {
'POST': ['first_field']
'PATCH': []
}
I need more space than comments provide to make my meaning clear. So here is what I suggest:
Different formatting means different serializers.
So here you have, for instance a MyModelSerializer and a MyModelCreationSerializer. Either create them independently, or have one inherit the other and specialize it (if it makes sense).
Use the appropriate GenericAPIView hook to return the correct serializer class depending on self.action. A very basic example could be:
class MyModelDetailAPI(GenericAPIView):
# serializer_class = unneeded as we override the hook below
def get_serializer_class(self):
if self.action == 'create':
return MyModelCreationSerializer
return MyModelSerializer
Default actions in regular viewsets are documented here, they are:
create: POST method on base route url
list: GET method on base route url
retrieve: GET method on object url
update: PUT method on object url
partial_update: PATCH method on object url
destroy: DELETE method on object url

Can Django-Rest-Framework create an API that returns a single attribute of an Object Model instance?

In my Django app, I have the following Models:
class MyModelA(models.Model):
myAttributeA = models.CharField(max_length=255)
class MyModelB(models.Model):
myParent = models.OneToOneField(myModelA)
myAttributeB = models.CharField(max_length=255)
My settings.py has the following Rest Permission settings:
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',),
I also have the following Serializers:
class MyModelASerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = MyModelA
fields = ('myAttributeA',)
class MyModelBSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = MyModelB
fields = ('myParent', 'myAttributeB',)
Now I want to write a simple Django-Rest-Framework API that will allow any user (weather authenticated or not) to retrieve the value of myParent on any instance of MyModelB assuming they have the Primary Key of the MyModelB instance. This should be rather simple. I'm not trying to update, create, or delete anything. I just want to retrieve the value of one attribute of the instance. But I also want my urls.py to match this endpoint to the API:
url(r'^api/AttrMyModelA/(?P<myModelAID>\d+)/?$', SOMETHING HERE. NOT SURE WHAT)
However, I cannot figure out which pattern to use from the tutorial to make this work. Should I use function based or class based views? Should I use Generic API views? Do I need a decorator or no? Mixins? Can someone please show me what my view should look like and what the urls.py endpoint should look like?
Thanks
You require a RetrieveAPIView-derived class to tie things together:
class MyModelAView(RetrieveAPIView):
model = MyModelA
serializer_class = MyModelASerializer
The route mentioned by you would then look like this:
url(r'^api/AttrMyModelA/(?P<pk>\d+)/?$', MyModelAView.as_view())
Note that pk is the default look-up field used by APIView-derived classes when performing single object queries.
You have defined a default permission class (in settings.py), so unless you want to override that you don't need to specify a permission_classes value in your view class.

Categories

Resources