How to make Django values() exclude some fields? - python

I have multiple models which has fields like "created_at" "updated_at" which I don't want to get with objects.values().
Does Django has any way to exclude fields in values()?
I know people refer to defer(), but it doesn't return QuerySet<Dict> like values() instead returns QuerySet<Model>.
I tried objects.defer("created_at", "updated_at").values(), but it includes those 2 deferred fields in the resulting Dict.
I see defer().query only selecting the non-exluded fields in the SQL, but using defer(..).values() resets the deferred fields and selects all fields.
I cannot specify which field I want, since different model has different fields, I can only specity which fields I don't want. So I cannot use values('name', 'age', ...)
I'm planning to use a CustomeManager, which I can use in all model.
Example:
class CustomManager(models.Manager):
def values_excluded(self):
return self.values() # somehow exlude the fields and return QuerySet<Dict>
class ExampleModel(models.Model):
name = models.CharField(max_length=20)
age = models.IntegerField()
created_at = models.DateTimeField()
updated_at = models.DateTimeField()
objects = CustomManager()
ExampleModel.objects.values_excluded()
Is there any way in Django or do I have to manually delete those keys from the resulting Dict from values()?

esclude_fields = ['created_at', 'updated_at']
keys = [f.name for f in Model._meta.local_fields if f.name not in esclude_fields]
queryset.values(*keys)

This should work:
class CustomManager(models.Manager):
def values_excluded(self, *excluded_fields):
included_fields = [f.name for f in self.model._meta.fields if f.name not in excluded_fields]
return self.values(*included_fields)

Related

Django REST Framework - Get reversed value of boolean field in serializer

I have 2 models:
class Model(models.Model):
...
related = models.ForeignKey(
'RelatedModel',
on_delete=models.CASCADE,
related_name='related_model'
)
class RelatedModel(models.Model):
...
flag = models.BooleanField()
I need to pass value of 'flag' attribute of RelatedModel in Model instance serializer and additionally this value must be reversed i.e. if it is 'True', I should return 'False' as boolean data type.
Already implemented this with method:
class ModelSerializer(serializers.ModelSerializer):
...
flag = serializers.SerializerMethodField()
#staticmethod
def get_flag(obj):
return not obj.related.flag
class Meta:
model = Model
fields = (
...
flag
)
But maybe there is opportunity to use only serializer fields like this but with reverse value?
flag = serializers.BooleanField(
source='related.flag', read_only=True
)
If you need to reverse the value you can' t use a BooleanField, the simplest solution is to use a SerializerMethodField as you have already done. Or you could also create a custom field class, but that is more complicated.

Modifying value on serialization - Django Rest Framework

I have a model which contains sensitive data, let's say a social security number, I would like to transform that data on serialization to display only the last four digits.
I have the full social security number stored: 123-45-6789.
I want my serializer output to contain: ***-**-6789
My model:
class Employee (models.Model):
name = models.CharField(max_length=64,null=True,blank=True)
ssn = models.CharField(max_length=16,null=True,blank=True)
My serializer:
class EmployeeSerializer(serializers.ModelSerializer):
id = serializers.ReadOnlyField()
class Meta:
model = Employee
fields = ('id','ssn')
read_only_fields = ['id']
You can use SerializerMethodField:
class EmployeeSerializer(serializers.ModelSerializer):
id = serializers.ReadOnlyField()
ssn = SerializerMethodField()
class Meta:
model = Employee
fields = ('id','ssn')
read_only_fields = ['id']
def get_ssn(self, obj):
return '***-**-{}'.format(obj.ssn.split('-')[-1]
If you don't need to update the ssn, just shadow the field with a SerializerMethodField and define get_ssn(self, obj) on the serializer.
Otherwise, the most straightforward way is probably to just override .to_representation():
def to_representation(self, obj):
data = super(EmployeeSerializer, self).to_representation(obj)
data['ssn'] = self.mask_ssn(data['ssn'])
return data
Please add special case handling ('ssn' in data) as necessary.
Elaborating on #dhke’s answer, if you want to be able to reuse this logic to modify serialization across multiple serializers, you can write your own field and use that as a field in your serializer, such as:
from rest_framework import serializers
from rest_framework.fields import CharField
from utils import mask_ssn
class SsnField(CharField):
def to_representation(self, obj):
val = super().to_representation(obj)
return mask_ssn(val) if val else val
class EmployeeSerializer(serializers.ModelSerializer):
ssn = SsnField()
class Meta:
model = Employee
fields = ('id', 'ssn')
read_only_fields = ['id']
You can also extend other fields like rest_framework.fields.ImageField to customize how image URLs are serialized (which can be nice if you’re using an image CDN on top of your images that lets you apply transformations to the images).

Django REST Framework serializer with different models

I have three different models that I want to gather in a feed type page. They do all contain different types of things but for simplicity, the models are the same in this instance.
class ObjectA(models.Model):
text = models.TextField()
pub_date = models.DateTimeField('date published',auto_now_add=True)
...
class ObjectB(models.Model):
text = models.TextField()
pub_date = models.DateTimeField('date published',auto_now_add=True)
...
class ObjectC(models.Model):
text = models.TextField()
pub_date = models.DateTimeField('date published',auto_now_add=True)
...
What would be the general idea to serialize lists of all three objects into one list ordered by pub_date using the Django REST Framework. I just have experience using the meta version below but it can only deal with one model I am assuming. Thanks in advance.
class ObjectAListSerializer(serializers.ModelSerializer):
class Meta:
model = ObjectA
fields = [
'text',
'pub_date'
]
Pretty much trying to create something that would work like this:
class AllObjectsListSerializer(serializers.ModelSerializer):
The most important thing here is that you shouldn't be having three different models here. If they are storing the same data, there should be only one model. To have three models means every time you need to execute a statement you need to precede that with a IF ELIF ELSE which is far from DRY. Not to mention the fact that you need to do a UNION as suggested by Wtower in his answer
Merge the models.
You need to make a union on the querysets:
query_union = query1 | query 2
And you need a custom serializer for the fields of that union. Or if the union fields are all the same from any of the models, possibly use those model's modelserializer, but haven't tested that one.
dataA = ASerializer(queryset_A, many=True).data
dataB = BSerializer(queryset_B, many=True).data
dataC = CSerializer(queryset_C, many=True).data
just
return Response(data={'dataA ': dataA , 'dataB ': dataB ,'dataC ': dataC })
If you want return a list,the item is {'a':a,''b':'b,'c':c},you should declare you relation between a,b,c and can filter b and c with a.If so,i will write an example for you.

How can i filter queryset in nested serializer in django

I have this code
# Models
class NestedSample(models.Model):
something = models.CharField(max_length=255)
class Sample(models.Model):
thing = models.BooleanField()
nested = models.ForeignKey(NestedSample)
# Serializers
class NestedSampleSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = api_models.NestedSample
class SampleSerializer(serializers.HyperlinkedModelSerializer):
nested = NestedSampleSerializer() # HERE filter delete=false
nested2 = NestedSample2Serializer() # HERE filter deletefalse
class Meta:
model = api_models.Sample
In my view I am overrding the queryset for delete=False but it is not applying to nested serializers.
delete=False in queryset will only filter Sample. To filter queryset in nested serializer you can use serializers.ListSerializer like:
class FilterDeleteListSerializer(serializers.ListSerializer):
def to_representation(self, data):
data = data.filter(delete=False)
return super(FilterDeleteListSerializer, self).to_representation(data)
class NestedSampleSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = api_models.NestedSample
list_serializer_class = FilterDeleteListSerializer
class NestedSample2Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = api_models.NestedSample2
list_serializer_class = FilterDeleteListSerializer
class SampleSerializer(serializers.HyperlinkedModelSerializer):
nested = NestedSampleSerializer() # HERE filter delete=false
nested2 = NestedSample2Serializer() # HERE filter deletefalse
class Meta:
model = api_models.Sample
Learn more here
I didn't exactly understand your question, but from what I figured you've got a boolean field in your Model which is set to True if you delete the object instead of actually deleting it from the database (SQL DELETE).
Now coming to your question, if you just want to filter the nested serializer then you could use the SerializerMethodField. You need to specify the method to call as an argument or add a method with the name 'get_' followed by the field name. In this method you can filter the queryset serialize it and return the data of that queryset.
class UserSerializer(serializers.ModelSerializer):
delete_filtered_items = serializers.SerializerMethodField()
class Meta:
model = User
def get_delete_filtered_items(self, obj):
items = Item.objects.filter(user=obj,deleted=False)
serializer = ItemsSerializer(instance=items, many=True)
return serializer.data
The above solution should work for your requirements, but if what you've implemented is similar to a soft delete then it would seem cleaner and more moduler to create a custom model manager.

how can I save a form with ModelMultipleChoiceField?

I have a model Calendar and in a form I want to be able to create multiple instances of it.
Here are my models:
class Event(models.Model):
user = models.ForeignKey(User)
class Group(models.Model):
name = models.CharField(_('Name'), max_length=80)
events = models.ManyToManyField(Event, through='Calendar')
class Calendar(models.Model):
event = models.ForeignKey(Event)
group = models.ForeignKey(Group)
class CalendarInline(admin.TabularInline):
model = Calendar
extra = 1
class GroupAdmin(admin.ModelAdmin):
inlines = (CalendarInline,)
Here is how I try to code my form:
class AddEventToGroupForm(ModelForm):
group = ModelMultipleChoiceField(queryset=Group.objects.all(), widget=SelectMultiple())
def save(self):
for g in self:
g.save()
class Meta:
model = Calendar
fields = ('group',)
And here is a part of my view:
e = Event.objects.get(id=event_id)
calentry = Calendar(event=e)
if request.POST:
f = AddEventToGroupForm(data=request.POST, instance=calentry)
if f.is_valid():
f.save()
If I try to submit that form, I get:
AttributeError at /groups/add_event/7/
'BoundField' object has no attribute 'save'
What is the proper way to create multiple instances of Calendar in this
situation?
That's not how to deal with many-to-many relationships in forms. You can't iterate through fields in a form and save them, it really doesn't work that way.
In this form, there's only one field, which happens to have multiple values. The thing to do here is to iterate through the values of this field, which you'll find in the cleaned_data dictionary (when the form is valid).
So, in your view, you do something like:
if f.is_valid():
for group in f.cleaned_data['group']:
calentry.groups.add(group)
Note you're not 'saving' the AddEventToGroupForm form at all. I would make it a standard forms.Form, rather than a ModelForm, as you're not really depending on any of the ModelForm functionality.

Categories