Django model instance fields with ManyToMany relationship to Dict - python

I'm trying to create a simple action that gets one record (with ManyToMany relationship) from the database then display the JSON serialized instance, here is how I did it for now:
the service model:
class SystemService(models.Model):
name = models.CharField(max_length=35, unique=True, null=False, blank=False)
verion = models.CharField(max_length=35, unique=True, null=False, blank=False)
def __str__(self):
return self.name
the server model:
class Server(models.Model):
name = models.CharField(max_length=35, unique=True, null=False, blank=False)
ip_address = models.GenericIPAddressField(protocol='both', unpack_ipv4=True,
null=False, blank=False, unique=True)
operating_system = models.ForeignKey(OperatingSystem, null=False, blank=False)
monitored_services = models.ManyToManyField(SystemService)
info = models.CharField(max_length=250, null=True, blank=True)
pause_monitoring = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
Here is how it is now using muj-gabriel answer:
def get_server(request, server_id):
try:
server_object = Server.objects.get(id=server_id)
data = {
'name': server_object.name,
'ip_address': server_object.ip_address,
'os': server_object.operating_system.name,
'info': server_object.info,
'monitoring_paused': server_object.pause_monitoring,
'created_at': server_object.created_at,
'update_at': server_object.updated_at,
'services': {service['id']: service['name'] for service
in server_object.monitored_services.values('id', 'name')}
}
return JsonResponse(data)
except Server.DoesNotExist:
return JsonResponse({'error': 'Selected object does not exits!'})
I don't think that what I did is good enough since I have to repeat the same thing each time I need to get one instance as JSON, so I would like to know if is there a pythonic and dynamic way to do it?

If you just need the values 'id' and 'name' I suggest using this:
'services': {service['id']: service['name']
for service in server_object.monitored_services.values('id', 'name')}
See django docs
Also you can move the code into a property to the Model class to reuse it elsewhere.
class Server(models.Model):
.......
#property
def data(self):
return {
'name': self.name,
'ip_address': self.ip_address,
'os': self.operating_system.name,
'info': self.info,
'monitoring_paused': self.pause_monitoring,
'created_at': self.created_at,
'update_at': self.updated_at,
'services': {service['id']: service['name'] for service in self.monitored_services.values('id', 'name')}
}
Your view function will be:
def get_server(request, server_id):
try:
server_object = Server.objects.get(id=server_id)
return JsonResponse(server_object.data)
except Server.DoesNotExist:
return JsonResponse({'error': 'Selected object does not exits!'})

After looking a bit in the Django doc I found the model_to_dict function, which basically do what I need (Model instance to Dict), but for the ManyToMany relationship it only returns a list of PKs, so I wrote my own function based on it:
def db_instance2dict(instance):
from django.db.models.fields.related import ManyToManyField
metas = instance._meta
data = {}
for f in chain(metas.concrete_fields, metas.many_to_many):
if isinstance(f, ManyToManyField):
data[str(f.name)] = {tmp_object.pk: db_instance2dict(tmp_object)
for tmp_object in f.value_from_object(instance)}
else:
data[str(f.name)] = str(getattr(instance, f.name, False))
return data
Hope it helps someone else.

Related

Django, Assign specific fuction tomodel field and then call it

I have a model built like this
class ApiPartner(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=30, verbose_name=_('Name'))
api_key = models.CharField(max_length=50, verbose_name=_('API key'), null=True)
secret_key = models.CharField(max_length=50, verbose_name=_('Secret key'), null=True)
client_key = models.CharField(max_length=50, verbose_name=_('Client key'), null=True)
endpoint = models.CharField(max_length=50, verbose_name=_('Endpoint'), null=True)
logo = models.ImageField(upload_to='logos/', null=True)
evey API partner has its own method to retrieve data, for example
def get_youtube_posts(endpoint,api_key):
results=list(requests.get(endpoint+'?key='+api_key).json())
return results[0:50]
def get_instagram_posts(endpoint,api_key,secret_key):
return requests.get(endpoint+'?key='+api_key+'&secret='+secret_key)
the question is: how do i assign the 'get_posts' function to the model so i can call a generic ApiPartner.get_posts() and it will retrieve the posts using the given function?
I'm thinking about like a models.FunctionField but i know that doesn't exist.
I think this is more a logical problem than a technical one but i can't find a way. Thank you
Maybe I'm understanding the question wrong; but you can just assign it as a property on the model class:
class MyModel(models.Model):
fields...
#property
def youtube_posts(self):
results=list(requests.get(self.endpoint+'?key='+self.api_key).json())
return results[0:50]
#property
def instagram_posts(self):
return requests.get(self.endpoint+'?key='+self.api_key+'&secret='+self.secret_key)
Then you can call it with the instance of your model.
mymodel = MyModel.objects.all().first()
youtube_posts = mymodel.youtube_posts
# or
instagram_posts = mymodel.instagram_posts
But this will only return one or the other since your models are based on one specific endpoint.
To create a more generic method on the model, use the above methods, plus this:
#property
def platform_posts(self)
if "instagram" in self.endpoint:
return self.instagram_posts
elif "youtube" in self.endpoint:
return self.youtube_posts
... You get the gist.

Django query certain attribute via foreign key

I want to query certain data from a foreign key class.
Is there a way to make it directly in the query?
I want to do the following query:
plant_list = Plant.objects.filter(plant_product = ikey).values('plant_number', 'plant_name', 'plant_city', 'plant_country', 'plant_product')
My Result is:
{'plant_number': '0001', 'plant_name': 'HoP1', 'plant_city': 3, 'plant_country': 1, 'plant_product': 1}
Now, for example at plant_city. I donĀ“t want to have the ID I want to have the attribute city_name of the model City, which is the Foreign Key.
So here ist the result I want:
{'plant_number': '0001', 'plant_name': 'HoP1', 'plant_city': 'Homburg', 'plant_country': 'Germany', 'plant_product': 1}
Is there a elegant way?
That would be a very graet help!
Here are my Models:
class City(models.Model):
city_name = models.CharField(max_length=100, unique=True)
class Meta:
ordering = ['city_name']
# changes view in admin area
verbose_name_plural = ('Cities')
def __str__(self):
return self.city_name
class Country(models.Model):
country_name = models.CharField(max_length=100, unique=True)
class Meta:
ordering = ['country_name']
verbose_name_plural = 'Countries'
def __str__(self):
return self.country_name
class Plant(models.Model):
plant_number = models.CharField(max_length=10, unique=True)
plant_name = models.CharField(max_length=10, unique=True)
plant_city = models.ForeignKey(City, on_delete=models.CASCADE)
plant_country = models.ForeignKey(Country, on_delete=models.CASCADE)
plant_product = models.ManyToManyField(TPZProductCluster1)
updated_at = models.DateField(auto_now=True)
class Meta:
ordering = ['plant_number']
def plant_produces(self):
return ', '.join([p.pc1_product for p in self.plant_product.all()])
def __str__(self):
return self.plant_name + " (" + str(self.plant_number) +")"
Thank you in advance!!!
If you want to include the name of the city, you can work with an F object:
from django.db.models import F
plant_list = Plant.objects.filter(
plant_product=ikey
).values(
'plant_number',
'plant_name',
'plant_country',
'plant_product'
city=F('plant_city__city_name')
)
That being said, usually using .values() is not a good idea. If you want to serialize data to a JSON blob for example, it is better to work with serializers, for example with the Django REST framework.
With help of Willem I found the answer.
Here my Query:
plant_list = Plant.objects.filter(plant_product__pc1_product=ikey).values(
'plant_number',
'plant_name',
'plant_city__city_name',
'plant_country__country_name',
'plant_product__pc1_product'
)
Here my result:
{'plant_number': '0001', 'plant_name': 'HoP1', 'plant_city__city_name': 'Homburg', 'plant_country__country_name': 'Germany', 'plant_product__pc1_product': 'CRI-MV'}
Thanks Willem that was a great help!!!!

Django rest framework - NOT NULL constraint on a foreign Key

I have this Error :
IntegrityError at /api/post_flight_schedule/
NOT NULL constraint failed: flights_tailnumber.aircraft_type_id
When I try to add a new PosFlightSchedule object to DB over http://127.0.0.1:8000/api/pos_flight_schedule (Website/APIView)
I have the below serializer :
class PosFlightScheduleModelSerializer(ModelSerializer):
class Meta:
model = PosFlightSchedule
fields = ['pos_route_id', 'tail_number', 'pos_flight_number', 'pos_flight_departure_time', 'pos_flight_date',
'pax_count']
class PosFlightScheduleSerializer(serializers.Serializer):
pos_route_id = serializers.CharField(source='pos_route_id.route_id', read_only=False)
tail_number = serializers.CharField(source='tail_number.tail_number', read_only=False)
pos_flight_number = serializers.CharField(source='pos_flight_number.flight_number', read_only=False)
pos_flight_departure_time = serializers.CharField(source='pos_flight_departure_time.flight_departure_time', allow_null=True,
read_only=False)
pos_flight_date = serializers.CharField(source='pos_flight_date.flight_date', read_only=False)
pax_count = serializers.IntegerField(read_only=False)
def create(self, validated_data):
tail_number_data = validated_data.pop("tail_number")
tail_number = TailNumber.objects.create(**tail_number_data)
flight_number_data = validated_data.pop("pos_flight_number")
flight_number = FlightSchedule.objects.create(**flight_number_data)
flight_departure_time_data = validated_data.pop("pos_flight_departure_time")
print "DEP_TIME" + str(flight_departure_time_data)
flight_departure_time = FlightSchedule.objects.create(**flight_departure_time_data)
route_id_data = validated_data.pop("pos_route_id")
route_id = FlightScheduleDetail.objects.create(**route_id_data)
flight_date_data = validated_data.pop("pos_flight_date")
flight_date = FlightScheduleDetail.objects.create(**flight_date_data)
pax_count = validated_data.pop("pax_count")
schedule_obj = PosFlightSchedule.objects.create(**validated_data)
# if tail_number:
schedule_obj.set_tail_number(tail_number)
schedule_obj.set_pos_flight_number(flight_number)
schedule_obj.set_pos_flight_departure_time(flight_departure_time)
schedule_obj.set_pos_route_id(route_id)
schedule_obj.set_pos_flight_date(flight_date)
schedule_obj.set_pax_count(pax_count)
schedule_obj.save()
return schedule_obj
def update(self, instance, validated_data):
tail_number = validated_data.pop("tail_number")
flight_number = validated_data.pop("pos_flight_number")
flight_departure_time = validated_data.pop("pos_flight_departure_time")
route_id = validated_data.pop("pos_route_id")
flight_date = validated_data.pop("pos_flight_date")
pax_count = validated_data.pop("pax_count")
instance.__dict__.update(validated_data)
if tail_number:
instance.set_tail_number(tail_number)
if flight_number:
instance.set_pos_flight_number(flight_number)
if flight_departure_time:
instance.set_pos_flight_departure_time(flight_departure_time)
if route_id:
instance.set_pos_route_id(route_id)
if flight_date:
instance.set_pos_flight_date(flight_date)
if pax_count:
instance.set_pax_count(pax_count)
instance.save()
return instance
The model of the field which is giving error looks like :
class TailNumber(models.Model):
tail_number_id = models.AutoField(null=False, primary_key=True)
tail_number = models.CharField(max_length=20, null=False, blank=False, unique=True)
aircraft_type = models.ForeignKey(AircraftType, null=False, blank=False)
def __unicode__(self):
return u'%s' % self.tail_number
class Meta:
verbose_name_plural = "Tail Numbers"
I am not understanding what is going wrong here.
The error you get is probably due to the fact that the dictionary tail_number_data does not contain the keyword aircraft_type, which is expected by TailNumber.objects to create the row in the db, since you defined it with no possibility to be null
aircraft_type = models.ForeignKey(AircraftType, null=False, blank=False)
^^^^^
Check that the key "aircraft_type" does exist in the dictionary tail_number_data, or allow for it to be null. Furthermore, if you consider the latter option and that this information is supposed to come from a UI, you may also want to allow for aircraft_type to be blank. See differentiate null=True, blank=True in django for details.

Django: append a count of manytomany object instances and then retrieve it in template

Okay, so I'm sure i'm making a simple error here but I'm at a loss as to what it might be at this point or where to even begin to find the cause of this failing...
I have 5 models, set up like so:
class Keyword(models.Model):
key = models.CharField(max_length=2000, unique=True)
def __str__(self):
return self.key
class Entries(models.Model):
name = models.CharField("Name", max_length=200)
updated = models.DateTimeField("Last Updated", auto_now=True)
key_list = models.ManyToManyField(Keyword, blank=True, verbose_name="Keywords")
description = models.TextField("Description", blank=True)
class Meta:
abstract = True
class Employee(Entries):
uid= models.SlugField("UserID", max_length=6, unique=True, blank=True)
manager = models.SlugField("Manager's UserID", max_length=6)
def __str__(self):
return self.name
class Vendor(Entries):
company = models.CharField("Vendor Company", max_length=200)
email = models.EmailField("Vendor's Company Email Address", max_length=254, unique=True)
vend_man_name = models.CharField("Manager's Name", max_length=200)
vend_man_email = models.EmailField("Manager's Email Address", max_length=254)
def __str__(self):
return self.name
class Application(Entries):
app_url = models.URLField("Application URL", max_length=800, unique=True)
def __str__(self):
return self.name
class Machine(Entries):
address = models.CharField("Machine Address", max_length=800, unique=True)
phys_loc = models.TextField("Physical Location", blank=True)
def __str__(self):
return self.name
and my templatetag to display the data is thus:
#register.inclusion_tag('Labswho/key_cloud.html')
def key_cloud(keys_selected=''):
key_list = Keyword.objects.all()
for entry in key_list:
counter = Keyword.objects.filter(employee__key_list__key__contains=entry).distinct().count()
counter += Keyword.objects.filter(vendor__key_list__key__contains=entry).distinct().count()
counter += Keyword.objects.filter(application__key_list__key__contains=entry).distinct().count()
counter += Keyword.objects.filter(machine__key_list__key__contains=entry).distinct().count()
entry.append({'counter': counter})
context = {'key_list': key_list}
return context
but it keeps throwing me errors.
What I want it to do is to grab a count of the instances of each keyword on each model, add those instances together, and then give me a number I can reference in the template. My end goal is that I can use that number to set the font size so that keywords used more frequently will be visibly larger then ones used less frequently. I think what I want is for it to set a dictionary within each entry of my key_list, which i can then call for that number in the template when setting the font size. Not sure if i'm over-complicating this or how to fix it at this point so that it actually works...
(This is using Python 3.5 & Django 1.10)

How to save a many to many model in django

I have the following two model class in django.
class Rule(models.Model):
name = models.CharField(max_length=50)
user = models.ForeignKey(User, related_name='rules', null=True, blank=True)
threshold = models.CharField(max_length=50, null=True, blank=True)
alert_value = models.CharField(max_length=50, null=True, blank=True)
is_internal = models.BooleanField(default=False)
def __unicode__(self):
return self.name
def to_json(self):
return {
'name': self.name,
'threshold': self.threshold,
'alert_value': self.alert_value
}
class Module(models.Model):
name = models.CharField(max_length=50)
description = models.TextField(null=True, blank=True)
is_internal = models.BooleanField(default=False)
rules = models.ManyToManyField(Rule)
def to_json(self):
return {
'name': self.name,
'description': self.description,
'rules': [r.to_json() for r in self.rules.all()]
}
def __unicode__(self):
return self.name
Now I have the following code to save a Module object which implicitly contains a rules object in my view.py
def create_module(request):
if request.method == 'POST':
module_name = request.POST.get('name')
module_description = request.POST.get('description')
rule_ids = request.POST.getlist('rule_id')
rules = None
for rule_id in rule_ids:
try:
rules = models.Rule.objects.filter(pk__in=rule_id)
except models.Rule.DoesNotExist:
pass
module = models.Module(name=module_name,
description=module_description,
rules=rules)
module.save()
I get the rules correctly here but when save gets called I get an error
Exception Type: TypeError at /modules/create/
Exception Value: 'rules' is an invalid keyword argument for this function
How to overcome this when I want to save an object graph.
rules is not really a field on the model, it's an entry in a linking table - so it can't be saved until the Module entry exists. Also note that your loop is such that it will never consist of more than one Rules object, because you overwrite the rules variable each time. Instead you should simply get all the Rules and add them in one go.
module = models.Module(name=module_name,
description=module_description)
module.save()
rules = models.Rule.objects.filter(pk__in=rule_ids)
module.rules = rules
There's no need to save again after that: assigning to a related queryset does the database operation automatically. Also note that filter will not raise a DoesNotExist exception: if there is no matching rule, then there simply won't be an element in the resulting queryset.
you are overriding the rules queryset inside try and filter() doesnot raise DoesNotExist exception btw..
try this:
module = models.Module(name=module_name,description=module_description)
module.save()
#first save 'module' and add 'rules' from filter()-result
rules = models.Rule.objects.filter(pk__in=rule_ids)
module.rules = rules
module.save()
more about how to save m2m in django

Categories