Add new object to model with a foreign key - django

So this seems to be asked before but for the life of me, i cannot get any of the solutions to work.
I have two classes, Device and Log. There are many logs per device and i'd like to be able to add new items to the Log objects.
class Device(models.Models):
name = models.Charfield(max_length=100)
type = models.Charfield(max_length=100)
class Log(models.Modles):
device = models.ForeignKey(Device, related_name='msgs', on_delete=models.CASCADE)
log = models.Charfield(max_length=100)
date_time = models.DateTimeField(auto_now=True)
I've been trying things like this in my view:
device = Device.objects.filter(name=hostname)
device.msgs.add(log=new_log_msg)
but nothing i try is working. any ideas?

You need a Device instance, but filter always gives a queryset. You should use get.
device = Device.objects.get(name=hostname)
device.msgs.add(log=new_log_msg)

Get device object as
device = Device.objects.filter(name=hostname) # If hostname not found throw exception
For creating a new log
log = Log.objects.create(device=device, log='')

use get instead of filter in queryset and for creating and updating the alway intitiate the instance.
device = Device.objects.get(name=hostname)
device.msgs.add(log=new_log_msg)
For adding data in log
Log.objects.create(device=device, log='')
The device=device is foriegn key.

Related

How to Update models field based on another field of same models

class PurchaseOrder(models.Model):
purchase_order_id = models.AutoField(primary_key=True)
purchase_order_number = models.CharField(unique=True)
vendor = models.ForeignKey(Vendor)
i am creating Purchase Order(po) table. when po created i have to update purchase_order_number as "PO0"+purchase_order_id ex PO0123 (123 is Primary key). so i am using def save in models to accomplish this
def save(self):
if self.purchase_order_id is not None:
self.purchase_order_number = "PO"+str(self.purchase_order_id)
return super(PurchaseOrder, self).save()
It is working fine with single creation but when i try to create bulk of data using locust(Testing tool) its giving an error duplicate entry for PurchseOrdernumber Can we modify field value in models itself some thing like this
purchase_order_number = models.CharField(unique=True,default=("PO"+self.purchase_order_id )
To be honest, I don't think it should work when you create multiple instances. Because as I can see from the code:
if self.purchase_order_id is not None:
self.purchase_order_number = "PO"+str(self.purchase_order_id)
Here purchase_order_id will be None when you are creating new instance. Also, until you call super(PurchaseOrder, self).save(), it will not generate purchase_order_id, meaning purchase_order_number will be empty.
So, what I would recommend is to not store this information in DB. Its basically the same as purchase_order_id with PO in front of it. Instead you can use a property method to get the same value. Like this:
class PurchaseOrder(models.Model):
purchase_order_id = models.AutoField(primary_key=True)
# need to remove `purchase_order_number = models.CharField(unique=True)`
...
#property
def purchase_order_number(self):
return "PO{}".format(self.purchase_order_id)
So, you can also see the purchase_order_number like this:
p = PurchaseOrder.objects.first()
p.purchase_order_number
Downside of this solution is that, you can't make any query on the property field. But I don't think it would be necessary anyway, because you can do the same query for the purchase_order_id, ie PurchaseOrder.objects.filter(purchase_order_id=1).

Iterate through all related objects changing a value

I'm trying to work out a way i can iterate through each related object and change the 'show' value from True to False.
class Device(models.Models):
name = models.Charfield(max_length=100)
type = models.Charfield(max_length=100)
class Log(models.Modles):
device = models.ForeignKey(Device, related_name='msgs', on_delete=models.CASCADE)
log = models.Charfield(max_length=100)
date_time = models.DateTimeField(auto_now=True)
show = models.BooleanField(default=True)
I've tried the following but it's not working.
device = Device.objects
for host in device.all():
Log.objects.filter(device=host).update(show=False)
but i get 'Log' object has no attribute 'update'. any ideas?
Edit: Just to make it more clear what i'm trying to do. There are multiple 'Log' objects per 'Device' object. On a particular view i'd like to set the 'show' value to false because that means they have all been read and don't need to show in the top bar of my site any more.
ok answer is this.
selected_device = Device.objects.get(id.self.kwargs['device_id']
Log.objects.filter(device=selected_device).update(show=False)
I don't know why this works but it does. i can't see how it iterates through each log per device but it does so i'm just going to accept it works.

Adding to model with OneToMany and updating existing entries.

I have a couple of Django model questions. I am running the following code as a Django manage extension, which I am new to.
1) I am not certain my "Location.objects.get(city=key)" is correct for OneToMany in my add_server_to_db function. I suspect this is incorrect?
2) How can I harden this so if this is executed twice, it will update existing Server entries vs. error out?
Django Server Model:
class Servers(models.Model):
name = models.CharField(('name'), max_length=128)
location = models.OneToOneField('locations.Location', on_delete=models.CASCADE)
ip_address = models.CharField(('ip_address'), max_length=128)
date = models.DateField(auto_now=True)
Django Location Model:
class Location(models.Model):
city = models.CharField(('city'), max_length=10)
geolocation = models.PointField(('location'))
Function:
def add_server_to_db(data_dict):
print(data_dict)
for key, val in data_dict.items():
loc = Location.objects.get(city=key)
m = Server(
location=loc,
name=val['name'],
ip_address=val['ip_address'],
m.save()
Thanks.
I don't understand your question 1; there's nothing wrong with that line, and nothing specific to "one-to-many" in any case.
To prevent this creating new entries every time, you should use update_or_create:
loc = Location.objects.get(city=key)
Server.objects.update_or_create(
location=loc,
defaults={'name': val['name'], 'ip_address': val['ip_address']}
)
There's no need to call save() after this.

Django - join two models

For the following models I want to retrieve all the devices that have an entry in the History table with transition_date between a specified interval:
class History(models.Model):
device = models.ForeignKey(DeviceModel, to_field='id')
transition_date = models.DateTimeField()
class Meta:
db_table = 'History'
class DeviceModel(models.Model):
id = models.IntegerField()
name = models.CharField()
class Meta:
db_table = 'Devices'
I have this code that filters for the specified interval:
devices = DeviceModel.objects.filter(history__transition_date__range=(startDate, endDate))
That gives me as many rows as History table has with transition_date in the specified range.
The filter function performs an INNER JOIN between DeviceModel and History on device id retrieving only DeviceModel fields. My question is how do I retrieve data from both History and DeviceModel at the same time while joining them as with filter/select_related on device id.
I'd rather not write a custom SQL query.
In your models Device and History models are related with a foreign key from History to DeviceModel, this mean when you have a History object you can retrieve the Device model related to it, and viceversa (if you have a Device you can get its History).
Example:
first_history = History.objects.all()[0]
first_history.device # This return the device object related with first_history
first_history.device.name # This return the name of the device related with first_history
But it works also in the other way, you could do:
first_device = Device.objects.all()[0]
first_device.history # This return the history object related with device
first_device.history.transition_date # Exactly as before, can access history fields
So in your query:
devices = DeviceModel.objects.filter(history__transition_date__range=(startDate, endDate))
This return a device list, but you can access to the history related with each device object
Isn't that enough for you ? You have a Device list, and each device can access to its related History object
Info: When you declare a ForeignKey field the models are related by id for default, I say this because you're doing:
device = models.ForeignKey(DeviceModel, to_field='id')
as you can see you're using to_field='id' but this relation is done by default, if you do:
device = models.ForeignKey(DeviceModel)
You'll get same results
(EDIT) Using .values() to obtain list [device.name, history.date]
To get a list like you said [device.name, history.date] you can use .values() function of Django QuerySet, official documentation here
You can try something like:
devices = DeviceModel.objects.filter(history__transition_date__range=(startDate, endDate)).values('name','history__transition_date')
# Notice that it is 'history _ _ transition_date with 2 underscores

Django Save Model Idiom

Let's say you've got a ActiveDirectoryUser model:
class ActiveDirectoryUser(models.Model):
object_guid = models.CharField(max_length=200, unique=True)
mail = models.EmailField(unique=True)
name = models.CharField(max_length=200)
This model is populated from an Active Directory query. Every day, I rerun a job in batch that queries Active Directory, gets the results back, and checks if the person is already in AD. If they are it checks if anything has changed, and if not they get added to the Database. I found myself writing:
try:
ad_user = ActiveDirectoryUser.objects.get(object_guid=object_guid)
ad_user.object_guid = object_guid
ad_user.mail = mail
ad_user.name = name
ad_user.save()
except ActiveDirectoryUser.DoesNotExist:
ActiveDirectoryUser(
object_guid=object_guid,
mail=mail,
name=name).save()
Basically, I try to get the object with the attributes and if that throws a DoesNotExist exception, I know it's not already there and so I create a new one. Is this the right/best/idiomatic way of either updating or saving a new object into the database? I know it works, but it looks wrong somehow. Inelegant.
No, django has a built-in way to do this.
entry, created = ActiveDirectory.objects.get_or_create(
object_guid = 'your_value',
name = = 'your_value',
mail = 'your_value'
)