Access model's foreign key related fields using a string? - django

A similar question was asked here: Access Django model's fields using a string instead of dot syntax?
I want to be able to access the value of a foreign-key related field in a django model, based on a dynamically generated string. For example, to get a direct field value you would use:
getattr(instance, 'name')
But I want to be able to follow foreign key relations, perhaps like so:
model_getattr(instance, 'name__othername')
This would require another query, and it might be possible to write the function model_getattr by splitting the attribute string on '__'. But I'm curious to know if there's a "correct" way to do this? Is it even possible?
UPDATE:
I managed to write a function that does what I need, but I would still like to know if there's a preferred way. This solution is obviously not as efficient as it could be, but it works for now:
def get_attribute(instance, name):
if hasattr(instance, name):
return getattr(instance, name)
names = name.split('__')
name = names.pop(0)
if len(names) == 0:
return None
if hasattr(instance, name):
value = getattr(instance, name)
return get_attribute(value, '__'.join(names))
return None

I think you need something which is already discussed in below thread
How to get foreign key values with getattr from models

Related

Django: check if ManyToManyField is empty from a Field object

This seems super simple but surprisingly could not find any clue on SO or Django docs yet.
I want to check if a particular ManyToManyField is empty but cant figure out a way to do it yet. Here's my exact usecase, if it helps:
for field in school._meta.get_fields(): # school is the Model object
if (field.get_internal_type() == 'ManyToManyField'):
#if (find somehow if this m2m field is empty)
#do something if empty
else:
if (field.value_to_string(self) and field.value_to_string(self)!='None'):
#do something when other fields are blank or null
Found this post which looks similar but is about filtering all ManyToManyFields that are empty in a Model object, so doesn't help the case above.
all() or count() or empty() or exists() don't seem to work on ManyToManyFields.
if (field): returns True (since its referring to the Manager)
Didn't find a relevant option in the Field reference or ManyToManyField reference
getattr(school,field.name).exists() worked for me. But all ears to know if there's a better approach
(i.e querying on model_object.field instead of field object)
How about using first() instead? That should definitely run quicker than all() and may run quicker than count().
IE:
def __str__(self):
if self.m2mField.first():
print('Object where m2mField contains stuff.')
else:
print('Object with nothing in m2mField.')
In my case I was overriding the clean() method for a model and had to perform validation if the m2m field was empty. getattr() returned an exception so I had to use .count(). This should also work for django-modelcluster's ParentalManyToMany fields.
def clean(self, *args, **kwargs):
if self.m2m_field.count() == 0:
raise ValidationError("No children")

Concise way of getting or creating an object with given field values

Suppose I have:
from django.db import models
class MyContentClass(models.Model):
content = models.TextField()
another_field = models.TextField()
x = MyContentClass(content="Hello, world!", another_field="More Info")
Is there a more concise way to perform the following logic?
existing = MyContentClass.objects.filter(content=x.content, another_field=x.another_field)
if existing:
x = existing[0]
else:
x.save()
# x now points to an object which is saved to the DB,
# either one we've just saved there or one that already existed
# with the same field values we're interested in.
Specifically:
Is there a way to query for both (all) fields without specifying
each one separately?
Is there a better idiom for either getting the old object or saving the new one? Something like get_or_create, but which accepts an object as a parameter?
Assume the code which does the saving is separate from the code which generates the initial MyContentClass instance which we need to compare to. This is typical of a case where you have a function which returns a model object without also saving it.
You could convert x to a dictionary with
x_data = x.__dict__
Then that could be passed into the object's get_or_create method.
MyContentClass.objects.get_or_create(**x_data)
The problem with this is that there are a few fields that will cause this to error out (eg the unique ID, or the _state Django modelstate field). However, if you pop() those out of the dictionary beforehand, then you'd probably be good to go :)
cleaned_dict = remove_unneeded_fields(x_data)
MyContentClass.objects.get_or_create(**cleaned_dict)
def remove_unneeded_fields(x_data):
unneeded_fields = [
'_state',
'id',
# Whatever other fields you don't want the new obj to have
# eg any field marked as 'unique'
]
for field in unneeded_fields:
del x_data[field]
return x_data
EDIT
To avoid issues associated with having to maintain a whitelist/blacklist of fields you, could do something like this:
def remove_unneeded_fields(x_data, MyObjModel):
cleaned_data = {}
for field in MyObjModel._meta.fields:
if not field.unique:
cleaned_data[field.name] = x_data[field.name]
return cleaned_Data
There would probably have to be more validation than simply checking that the field is not unique, but this might offer some flexibility when it comes to minor model field changes.
I would suggest to create a custom manager for those models and add the functions you want to do with the models (like a custom get_or_create function).
https://docs.djangoproject.com/en/1.10/topics/db/managers/#custom-managers
This would be the cleanest way and involves no hacking. :)
You can create specific managers for specific models or create a superclass with functions you want for all models.
If you just want to add a second manager with a different name, beware that it will become the default manager if you don't set the objects manager first (https://docs.djangoproject.com/en/1.10/topics/db/managers/#default-managers)

What is the read/write equivalent of serializers.StringRelatedField?

In Django Rest Framework 3, I want to return the unicode value of a pk relationship, the way you can using a serializer.StringRelatedField, but I need the value to be writable, too. StringRelatedField is read only.
I don't care if the API accepts the pk, or the string value, on the PUT (though accepting the string would be nifty, and would save me grabbing all the pks!). The API just needs to return the unicode string value on the GET.
I'm thinking PrimaryKeyRelatedField might be the way to go, but what does the query look like?
For instance, if the model I want is "Model", and I want Model.name to be serialized, what does this command look like:
name = serializers.PrimaryKeyRelatedField(queryset=Model.objects.get(pk=????))
I'm struggling because I don't know how to get the pk from the serializer object in order to query the related model ...
That's presuming PrimaryKeyRelatedField is what I need, of course. Which may be totally wrong.
Thanks
John
Here are example models as requested, slightly changed for clarity:
class CarModel(models.Model):
name = models.CharField(max_length=100,unique=True)
def __str__(self):
return self.name
class Car(models.Model):
name = models.CharField(max_length=100)
make = models.ForeignKey(CarMake)
car_model = models.ForeignKey(CarModel)
class CarSerializer(serializers.ModelSerializer):
car_model = serializers.StringRelatedField() //like this, but read/write
class Meta:
model = Car
In this example I'm serializing Car, trying to return a string value of CarModel that can be updated with a select dropdown in a form.
If I use different serializers, one for POST that expects the PK and one for everything else that returns the string, the select directive in the form gets very messy.
So, ideally, I just want to be able to POST the string value, and have the API complete the lookup and save the string as a PK.
"I just want to be able to POST the string value, and have the API complete the lookup and save the string as a PK."
That would imply that 'name' should be unique. If it isn't unique then the lookup might return several instances. In the example you currently have 'name' isn't unique, but if it was then you could use...
car_model = serializers.SlugRelatedField(queryset=..., lookup_field='name')
I'm not convinced if that's actually what you want though. The best way to clarify these sorts of questions is typically to forget about the code for a moment, and just focus on a precise description of what you want the input and output representations to look like?...

Django - how to write custom queryset per-field instead instead of per-model

I want to create a custom field such that if the field is queried, then the filter is always __iexact.
Example:
class Domain(models.Model):
domain = models.IExactCharField()
name = models.CharField()
I want a query like Domain.objects.filter('domain=newdomain') to be rewritten as Domain.objects.filter('domain__iexact=newdomain').
I understand you can do this with a custom manager, but I want adding the field to add the custom manager. And if a custom manager is already defined, I want the managers functionality to be chained. Is this possible? I was looking at the contribute_to_class method and thought it might have some potential when defining the field.
The code below won't work - the lookup_type is being added elsewhere (in QuerySet) unfortunately. I'll take a look inside :)
class IExactCharField(models.CharField):
def get_prep_lookup(self, lookup_type, value):
return super(IExactCharField, self).get_prep_lookup('iexact', value)
It enforces iexact lookups - you can evantually add check of lookup_type before referencing to superclass.
PS. Bear in mind that some databases (SQLite) won't support case insensitive unicode string lookups.
EDIT: the only workaround I have is to make all domain fields lowercase on save() or some save signal.

How to update multiple fields of a django model instance?

I'm wondering, what is a standard way of updating multiple fields of an instance of a model in django? ... If I have a model with some fields,
Class foomodel(models.Model):
field1 = models.CharField(max_length=10)
field2 = models.CharField(max_length=10)
field3 = models.CharField(max_length=10)
...
... and I instantiate it with one field given, and then in a separate step I want to provide the rest of the fields, how do I do that by just passing a dictionary or key value params? Possible?
In other words, say I have a dictionary with some data in it that has everything I want to write into an instance of that model. The model instance has been instantiated in a separate step and let's say it hasn't been persisted yet. I can say foo_instance.field1 = my_data_dict['field1'] for each field, but something tells me there should be a way of calling a method on the model instance where I just pass all of the field-value pairs at once and it updates them. Something like foo_instance.update(my_data_dict). I don't see any built-in methods like this, am I missing it or how is this efficiently done?
I have a feeling this is an obvious, RTM kind of question but I just haven't seen it in the docs.
It's tempting to mess with __dict__, but that won't apply to attributes inherited from a parent class.
You can either iterate over the dict to assign to the object:
for (key, value) in my_data_dict.items():
setattr(obj, key, value)
obj.save()
Or you can directly modify it from a queryset (making sure your query set only returns the object you're interested in):
FooModel.objects.filter(whatever="anything").update(**my_data_dict)
You could try this:
obj.__dict__.update(my_data_dict)
It seems like such a natural thing you'd want to do but like you I've not found it in the docs either. The docs do say you should sub-class save() on the model. And that's what I do.
def save(self, **kwargs):
mfields = iter(self._meta.fields)
mods = [(f.attname, kwargs[f.attname]) for f in mfields if f.attname in kwargs]
for fname, fval in mods: setattr(self, fname, fval)
super(MyModel, self).save()
I get primary key's name, use it to filter with Queryset.filter() and update with Queryset.update().
fooinstance = ...
# Find primary key and make a dict for filter
pk_name foomodel._meta.pk.name
filtr = {pk_name: getattr(fooinstance, pk_name)}
# Create a dict attribute to update
updat = {'name': 'foo', 'lastname': 'bar'}
# Apply
foomodel.objects.filter(**filtr).update(**updat)
This allows me to update an instance whatever the primary key.
Update using update()
Discussion.objects.filter(slug=d.slug)
.update(title=form_data['title'],
category=get_object_or_404(Category, pk=form_data['category']),
description=form_data['description'], closed=True)