Custom Related field - django

I've a serializer that receives and returns a list of strings.
Those strings internally are model instances.
In the serializer, when I receive a new types list I've to check if the type exists, if exists associate it to the other model otherwise create a new instance and associate it.
I can do it using a custom RelatedField
class TypeRelatedField(serializers.StringRelatedField):
def to_internal_value(self, data):
try:
return Type.objects.get(name=data)
except Type.DoesNotExist:
return Type.objects.create(name=data)
and in the serializer that receives the types list
types = TypeRelatedField(many=True, required=False)
so that if the type exists it will be returned, otherwise created. I'm not sure if it's the right place to do this, maybe I should do this in the create and update method?

If it works and doesn't break anything else, then it's a right thing to do ;) If you have to do this an all methods that manipulates model (create, update), then it's probably best to do it here, for DRY reasons. If not, do it in create or update. But if you need it for example only on create, you should write it in create, if only in update, then it should go there.

Related

It is a best practice use a Primary Key in a serializer who do not represent a Model Object?

I'm creating an endpoint in Django for a post REST API. I put the parameters of the post body on a class, to handle it internally, but the senior developer says it is better to use a serializer. I create a serializer and everything worked perfectly until the same senior says I have to add a PrimaryKeyRelatedField.
That is when my confusing started because this serializer is not for a Model, but for the body of the request object (who has 3 parameters, one mandatory and two optional), and when I added the mandatory parameters as PrimaryKeyRelatedField, I start to receive on the validated_data an empty OrderedDict()
My questions are:
It makes sense to have a PrimaryKeyRelatedField in a serializer who does not represent a Model?
In case it makes sense, how I can make it work (or why when I make one of the fields primary key, I receive an empty dict?)
PS: I make sure to send the right data to the endpoint, so this is not a case of receiving an empty OrderedDict because I did not send the mandatory field

Why is the difference between Serializer methods and View methods?

In Django DRF, I can add the following method to a serializer:
def create(self, validated_data):
user = User(
email=validated_data['email'],
username=validated_data['username']
)
user.set_password(validated_data['password'])
user.save()
return user
Previous to discovering this, I thought you typically did this within a view, by overriding a View/ViewSet methods.
I had thought that all the Serializer did was to transform the data and send it out or receive it from external calls.
Can someone explain to me the difference between doing this on the view vs doing it on a serializer?
And more broadly, what is the benefit of calling methods on the serializer instead of somewhere else?
A serialiser's job is to take one blob of data and convert it into another blob of data. Most typically it converts from a model to some form of dict and vice versa. You typically have at least one serialiser per model, but you may have any number of serialisers for different use cases. For example, you may get data in different forms, e.g. from a registration form and from an API call, and you want to convert both into a valid User instance. So you may define two different serialisers for those two different scenarios, which both end up with the same User instance. And you may need to customise some aspects of those serialisers to fit the occasion.
Views on the other hand take an HTTP request, do something and then decide what response to return. That doing something may involve using serialisers, but it doesn't have to. The biggest job of a view is to decide what to do when something succeeds or fails, like rendering a different response or redirecting to a different URL.
You need to decide how reusable some piece of logic is. Think of serialisers as transforming one type of data into another; call it converting type A into type B and vice versa. "Type" here being what your input/output data looks like exactly. Then consider where in your application you'll encounter a data blob of type A and whether you'll need to convert it into type B more than once. If so, you'll probably want to make a specific serialiser for it, instead of repeating the same logic inside two or more views.

I don't understand how Django-Rest-Framework serializers work

This may seem like a dumb question but I really feel like Django-Rest-Framework does not explain really well how it works. There is too much black box magic which confuses the configuration instructions (in my opinion).
For instance, it says I can override create or update within my serializer. So when my view post, I can send data to the serializer which has an update method declared.
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
def update(self, instance, validated_data):
Does update only get called when the model already exists and we're just updating some of it's data? Or does it call create when it creates a new one?
if I were to add this method to that class,
def create(self, validated_data):
return MyObject.objects.create(**validated_data)
is this specifically the method that must be called in order to add a new object? and your ability to override should be put in the serializer, but if not declared this is the default method with parameters that's being called?
There is too much black box magic which confuses the configuration instructions (in my opinion).
If there is something in the documentation that you think can be improved, feel free to submit a pull request with an update. If it seems reasonable, it will probably be merged and show up in a future release.
Or does it call create when it creates a new one?
create is called when a serializer is not initialized with a model instance, but only data is passed into it. Once serializer.save() is called, the serializer check if an instance was passed in and directly calls create so the model instance is created and saved in the database.
Does update only get called when the model already exists and we're just updating some of it's data?
update is called when a serializer is initialized with a model instance (or other object) and serializer.save() is called. This is roughly equivalent to Model.objects.update() when compared to Model.objects.create().
is this specifically the method that must be called in order to add a new object?
Serializers are designed to be saved (including object creation and updating) using the central serializer.save() method. This is similar to how a model can be saved or created using the model.save() method.
and your ability to override should be put in the serializer, but if not declared this is the default method with parameters that's being called?
It is recommended to override create and update on the serializer level if you need to change the logic for how a model and its related objects need to be saved, such as when working with nested serializers.

Django: many-to-one fields and data integrity

Let's say that I have a Person who runs an inventory system. Each Person has some Cars, and each Car has a very large number of Parts (thousands, let's say).
A Person, Bob, uses a Django form to create a Car. Now, Bob goes to create some Parts. It is only at the form level that Django knows that the Parts belong to some specific Car, and that the Parts.ForeignKey(Car) field should only have a specific Car as a choice. When creating a Part, you have to mess with the form's constructor or similar in order to limit the choice of Cars to only the cars owned by Bob.
It does not seem at all proper or secure to enforce this ownership at the form level. First, it seems that other users' Cars must be inaccessible to anyone but the owner of the Car; currently, only solid form programming prevents anyone from viewing any other Person's Cars! Second, it is seems sloppy to take care of the problem by modifying constructors in this manner. What do you all think about this, and is there any way to enforce this?
first of all part of this should happen at a level where you know who is the current user.
In Django theres nothing like a global request.user, so you need to check for this where you have a request object or pass it as a paremeter.
One way of achieving what you ask for is to use a custom manager. For the Car class you could use
class CarManager(models.Manager):
def for_user(self, user):
return super(CarManager, self).get_query_set().filter(creator=user)
You might also want to override the default manager and it's default methods to only allow this method.
For the second part, you don't need to modify the constructor there are other ways if you don't like it. But modifying them assures you for example you can't create a form without a logged user. Another way is to use a ModelChoiceField and override it's queryset attr in the view:
form.car.queryset = Car.objects.for_user(request.user)
but this approach is error prone. You should sitck to modifying the form constructor as this:
class PartForm(forms.ModelForm):
def __init__(self,user,*args,**kwargs):
super (PartForm,self ).__init__(*args,**kwargs)
self.fields['car'].queryset = Car.objects.for_user(user)
Hope this helps.

What are the options for overriding Django's cascading delete behaviour?

Django models generally handle the ON DELETE CASCADE behaviour quite adequately (in a way that works on databases that don't support it natively.)
However, I'm struggling to discover what is the best way to override this behaviour where it is not appropriate, in the following scenarios for example:
ON DELETE RESTRICT (i.e. prevent deleting an object if it has child records)
ON DELETE SET NULL (i.e. don't delete a child record, but set it's parent key to NULL instead to break the relationship)
Update other related data when a record is deleted (e.g. deleting an uploaded image file)
The following are the potential ways to achieve these that I am aware of:
Override the model's delete() method. While this sort of works, it is sidestepped when the records are deleted via a QuerySet. Also, every model's delete() must be overridden to make sure Django's code is never called and super() can't be called as it may use a QuerySet to delete child objects.
Use signals. This seems to be ideal as they are called when directly deleting the model or deleting via a QuerySet. However, there is no possibility to prevent a child object from being deleted so it is not usable to implement ON CASCADE RESTRICT or SET NULL.
Use a database engine that handles this properly (what does Django do in this case?)
Wait until Django supports it (and live with bugs until then...)
It seems like the first option is the only viable one, but it's ugly, throws the baby out with the bath water, and risks missing something when a new model/relation is added.
Am I missing something? Any recommendations?
Just a note for those who run into this issue as well, there is now an built-in solution in Django 1.3.
See the details in the documentation django.db.models.ForeignKey.on_delete Thanks for editor of Fragments of Code site to point it out.
The simplest possible scenario just add in your model FK field definition:
on_delete=models.SET_NULL
Django only emulates CASCADE behaviour.
According to discussion in Django Users Group the most adequate solutions are:
To repeat ON DELETE SET NULL scenario - manually do obj.rel_set.clear() (for every related model) before obj.delete().
To repeat ON DELETE RESTRICT scenario - manually check is obj.rel_set empty before obj.delete().
Ok, the following is the solution I've settled on, though it's far from satisfying.
I've added an abstract base class for all my models:
class MyModel(models.Model):
class Meta:
abstract = True
def pre_delete_handler(self):
pass
A signal handler catches any pre_delete events for subclasses of this model:
def pre_delete_handler(sender, instance, **kwargs):
if isinstance(instance, MyModel):
instance.pre_delete_handler()
models.signals.pre_delete.connect(pre_delete_handler)
In each of my models, I simulate any "ON DELETE RESTRICT" relations by throwing an exception from the pre_delete_handler method if a child record exists.
class RelatedRecordsExist(Exception): pass
class SomeModel(MyModel):
...
def pre_delete_handler(self):
if children.count():
raise RelatedRecordsExist("SomeModel has child records!")
This aborts the delete before any data is modified.
Unfortunately, it is not possible to update any data in the pre_delete signal (e.g. to emulate ON DELETE SET NULL) as the list of objects to delete has already been generated by Django before the signals are sent. Django does this to avoid getting stuck on circular references and to prevent signaling an object multiple times unnecessarily.
Ensuring a delete can be performed is now the responsibility of the calling code. To assist with this, each model has a prepare_delete() method that takes care of setting keys to NULL via self.related_set.clear() or similar:
class MyModel(models.Model):
...
def prepare_delete(self):
pass
To avoid having to change too much code in my views.py and models.py, the delete() method is overridden on MyModel to call prepare_delete():
class MyModel(models.Model):
...
def delete(self):
self.prepare_delete()
super(MyModel, self).delete()
This means that any deletes explicitly called via obj.delete() will work as expected, but if a delete has cascaded from a related object or is done via a queryset.delete() and the calling code hasn't ensured that all links are broken where necessary, then the pre_delete_handler will throw an exception.
And lastly, I've added a similar post_delete_handler method to the models that gets called on the post_delete signal and lets the model clear up any other data (for example deleting files for ImageFields.)
class MyModel(models.Model):
...
def post_delete_handler(self):
pass
def post_delete_handler(sender, instance, **kwargs):
if isinstance(instance, MyModel):
instance.post_delete_handler()
models.signals.post_delete.connect(post_delete_handler)
I hope that helps someone and that the code can be re-threaded back into something more useable without too much trouble.
Any suggestions on how to improve this are more than welcome.