Django Rest Framework: Disable save in update - django

It is my first question here, after reading the similar questions I did not find what I need, thanks for your help.
I am creating a fairly simple API but I want to use best practices at the security level.
Requirement: There is a table in SQL Server with +5 million records that I should ONLY allow READ (all fields) and UPDATE (one field). This is so that a data scientist consumes data from this table and through a predictive model (I think) can assign a value to each record.
For this I mainly need 2 things:
That only one field is updated despite sending all the fields of the table in the Json (I think I have achieved it with my serializer).
And, where I have problems, is in disabling the creation of new records when updating one that does not exist.
I am using an UpdateAPIView to allow trying to allow a bulk update using a json like this (subrrogate_key is in my table and I use lookup_field to:
[
{
"subrrogate_key": "A1",
"class": "A"
},
{
"subrrogate_key": "A2",
"class": "B"
},
{
"subrrogate_key": "A3",
"class": "C"
},
]
When using the partial_update methods use update and this perform_update and this finally calls save and the default operation is to insert a new record if the primary key (or the one specified in lookup_field) is not found.
If I overwrite them, how can I make a new record not be inserted, and only update the field if it exists?
I tried:
Model.objects.filter (subrrogate_key = ['subrrogate_key']). Update (class = ['class])
Model.objects.update_or_create (...)
They work fine if all the keys in the Json exist, because if a new one comes they will insert (I don't want this).
P.S. I use a translator, sorry.

perform_update will create a new record if you passed a serializer that doesn't have an instance. Depending on how you wrote your view, you can simply check if there is an instance in the serializer before calling save in perform_update to prevent creating a new record:
def perform_update(self, serializer):
if not serializer.instance:
return
serializer.save()

Django implements that feature through the use of either force_update or update_fields during save().
https://docs.djangoproject.com/en/3.2/ref/models/instances/#forcing-an-insert-or-update
https://docs.djangoproject.com/en/3.2/ref/models/instances/#specifying-which-fields-to-save
https://docs.djangoproject.com/en/3.2/ref/models/instances/#saving-objects
In some rare circumstances, it’s necessary to be able to force the
save() method to perform an SQL INSERT and not fall back to doing an
UPDATE. Or vice-versa: update, if possible, but not insert a new row.
In these cases you can pass the force_insert=True or force_update=True
parameters to the save() method.
model_obj.save(force_update=True)
or
model_obj.save(update_fields=['field1', 'field2'])

Related

Serializing Many to Many DRF

So I have predefined database structure like this:
User:
id
User_1:
user_=ForeignKey(User)
User_2:
user_1=ForeignKey(User_1)
Something:
owner=ForeignKey(User_2)
users=ManyToMany(User_2)
Well, I need to fill the table Something. Short story is that from front end I receive the ids of model User but to store I need User_2. One way I see right now is to write custom create method is serializer and do all manually.
Are there other ways to solve it?
And is it correct to use serializers.PrimaryKeyRelatedField to deal with ForeignKey when payload has next format?
{
"user": 1
}

Django-ORM: Check whether multiple items are in DB while minimizing calls

Assume that from an external API call, we get the following response:
resp = ['123', '67283', '99829', '786232']
These are external_id fields for our objects, defined in our Article model. Some of which may already exist in database, while others don't.
Before returning a response, we need to check whether each external_id corresponds to a record in our database, and if not, we need to create it and fetch additional info from another, third, source.
What is the most efficient way to do this? Right now I can't think of something better than:
for external_id in resp:
if not Article.objects.filter(external_id=external_id).exists():
# item doesn't exist, go fetch more data and create object
else:
# already exists, do something else
But there must be a better way..?
You can use sets for this task. Following code will issue only one database call:
expected_ids = set(int(pk) for pk in resp)
exist_ids = set(Article.objects.filter(external_id__in=resp)
.values_list('external_id', flat=True))
not_exist_ids = list(expected_ids - exist_ids)

Change model-object at PUT and update before GET in django-rest-framework

Sorry about the confusing title, but I don't know how to describe it better.
I need to run a model-function on the object I am editing using PUT in Django-Rest-Framework, which uses some of the new data from the PUT to calculate some new values it should save in the same model.
Example:
An item with {'amount': 2, 'price': 0, 'total': 0} is already stored in the database.
I am then updating price to 1 using a normal PUT request using django-rest-framework.
The model have a helperfunction called update_total() which I need to call to update the total field in the database (to, in this case 2 (2*1)).
The item is updated in the database, but the response returned from django-rest-framework is still showing total=0. After getting the object on new, total will be 2 as expected.
I need the response to be 2 in the response from the PUT, not after a regrab of the object. But how?
I have tried several things (which all doesn’t work):
Updating attrs in a validator to the new value.
Using post_save() in ListCreateAPIView to update the data.
Using pre_save() in ListCreateAPIView
Updating instance in restore_object() (even though it isn't for this purpose)
Does this look like a bug? Or is there another trick?
I kinda found a solution, but it feels somewhat dirty..
In my serializers restore_object I put code like this:
new_values = instance.update_counters()
for k, v in new_values.items():
self.data[k] = v
and in my models update_counters() function, I am returning a dict of what I changed..

Converting JSON to model instance in Django

What's the best way in django to update a model instance given a json representation of that model instance.
Is using deserialize the correct approach? Are there tutorials available out there?
The best approach would be to utilize one of the existing Django applications that support serializing model instances to and from JSON.
In either case, if you parse the JSON object to a Python dictionary, you can basically use the QuerySet.update() method directly.
So, say you get a dictionary where all the keys map to model attributes and they represent the values you'd want to update, you could do this:
updates = { # Our parsed JSON data
'pk': 1337,
'foo': 'bar',
'baz': 192.05
}
id = updates.pop('pk') # Extract the instance's ID
Foo.objects.filter(id=id).update(**updates) # Update the instance's data

Bulk create model objects in django

I have a lot of objects to save in database, and so I want to create Model instances with that.
With django, I can create all the models instances, with MyModel(data), and then I want to save them all.
Currently, I have something like that:
for item in items:
object = MyModel(name=item.name)
object.save()
I'm wondering if I can save a list of objects directly, eg:
objects = []
for item in items:
objects.append(MyModel(name=item.name))
objects.save_all()
How to save all the objects in one transaction?
as of the django development, there exists bulk_create as an object manager method which takes as input an array of objects created using the class constructor. check out django docs
Use bulk_create() method. It's standard in Django now.
Example:
Entry.objects.bulk_create([
Entry(headline="Django 1.0 Released"),
Entry(headline="Django 1.1 Announced"),
Entry(headline="Breaking: Django is awesome")
])
worked for me to use manual transaction handling for the loop(postgres 9.1):
from django.db import transaction
with transaction.atomic():
for item in items:
MyModel.objects.create(name=item.name)
in fact it's not the same, as 'native' database bulk insert, but it allows you to avoid/descrease transport/orms operations/sql query analyse costs
name = request.data.get('name')
period = request.data.get('period')
email = request.data.get('email')
prefix = request.data.get('prefix')
bulk_number = int(request.data.get('bulk_number'))
bulk_list = list()
for _ in range(bulk_number):
code = code_prefix + uuid.uuid4().hex.upper()
bulk_list.append(
DjangoModel(name=name, code=code, period=period, user=email))
bulk_msj = DjangoModel.objects.bulk_create(bulk_list)
Here is how to bulk-create entities from column-separated file, leaving aside all unquoting and un-escaping routines:
SomeModel(Model):
#classmethod
def from_file(model, file_obj, headers, delimiter):
model.objects.bulk_create([
model(**dict(zip(headers, line.split(delimiter))))
for line in file_obj],
batch_size=None)
Using create will cause one query per new item. If you want to reduce the number of INSERT queries, you'll need to use something else.
I've had some success using the Bulk Insert snippet, even though the snippet is quite old.
Perhaps there are some changes required to get it working again.
http://djangosnippets.org/snippets/446/
Check out this blog post on the bulkops module.
On my django 1.3 app, I have experienced significant speedup.
bulk_create() method is one of the ways to insert multiple records in the database table. How the bulk_create()
**
Event.objects.bulk_create([
Event(event_name="Event WF -001",event_type = "sensor_value"),
Entry(event_name="Event WT -002", event_type = "geozone"),
Entry(event_name="Event WD -001", event_type = "outage") ])
**
for a single line implementation, you can use a lambda expression in a map
map(lambda x:MyModel.objects.get_or_create(name=x), items)
Here, lambda matches each item in items list to x and create a Database record if necessary.
Lambda Documentation
The easiest way is to use the create Manager method, which creates and saves the object in a single step.
for item in items:
MyModel.objects.create(name=item.name)