Order object by field containing string value - python

I have a django model and a field representing an ip-address. I want order queryset of this model by ip-address value such "10.10.10.1"
I do Model.objects.order_by("ip_address"), but I get this
QuerySet["10.10.10.1", "10.10.10.11", "10.10.10.12", ...]
I want this QuerySet["10.10.10.1", "10.10.10.2", "10.10.10.3", ...]
I don't know how to do this.
Anyone have any ideas?

How I did it:
class IPAddressManager(models.Manager):
def get_queryset(self):
return super().get_queryset().extra(
select={'ordered_ip_addresses': "string_to_array(ip_address, '.')::int[]", },
)
def all_ordered_objects(self):
return self.order_by("ordered_ip_addresses")
class IPAddress(models.Model):
objects = IPAddressManager()
ip_address = models.CharField()
now when we will call IPAddress.objects.all_ordered_objects() we will have really ordered set

Related

Django - Can I add a calculated field that only exists for a particular sub-set or occurences of my model?

Imagine that you have a model with some date-time fields that can be categorized depending on the date. You make an annotation for the model with different cases that assign a different 'status' depending on the calculation for the date-time fields:
#Models.py
class Status(models.TextChoices):
status_1 = 'status_1'
status_2 = 'status_2'
status_3 = 'status_3'
special_status = 'special_status'
class MyModel(models.Model):
important_date_1 = models.DateField(null=True)
important_date_2 = models.DateField(null=True)
calculated_status = models.CharField(max_length=32, choices=Status.choices, default=None, null=True, blank=False,)
objects = MyModelCustomManager()
And the manager with which to do the calculation as annotations:
# managers.py
class MyModelCustomManager(models.Manager):
def get_queryset(self):
queryset = super().get_queryset().annotate(**{
'status': Case(
When(**{'important_date_1' is foo, 'then':
Value(Status.status_1)}),
When(**{'important_date_2' is fii, 'then':
Value(Status.status_2)}),
When(**{'important_date_1' is foo AND 'importante_date_2' is whatever, 'then':
Value(Status.status_3)}),
# And so on and so on
)
}
)
return queryset
Now, here's where it gets tricky. Only one of these sub-sets of occurrences on the model requires an ADDITIONAL CALCULATED FIELD that literally only exists for it, that looks something like this:
special_calculated_field = F('important_date_1') - F('importante_date_2') #Only for special_status
So, basically I want to make a calculated field with the condition that the model instance must belong to this specific status. I don't want to make it an annotation, because other instances of the model would always have this value set to Null or empty if it were a field or annotation and I feel like it would be a waste of a row in the database.
Is there way, for example to do this kind of query:
>>> my_model_instance = MyModel.objects.filter(status='special_status')
>>> my_model_instance.special_calculated_field
Thanks a lot in advance if anyone can chime in with some help.

Django-filter 2 use #property to filter?

I've got this filter:
class SchoolFilter(django_filters.FilterSet):
class Meta:
model = School
fields = {
'name': ['icontains'],
'special_id': ['icontains'],
}
Where special_id is a #property of the School Model:
#property
def special_id(self):
type = self.type
unique_id = self.unique_id
code = self.code
if unique_id < 10:
unique_id = f'0{unique_id}'
if int(self.code) < 10:
code = f'0{self.code}'
special_id = f'{code}{type}{id}'
return special_id
I've tried to google some answers, but couldn't find anything. Right now If I use my filter like I do I only receive this error:
'Meta.fields' contains fields that are not defined on this FilterSet: special_id
How could I define the property as a field for this FilterSet? Is it even possible for me to use django-filter with a #property?
Thanks for any answer!
Update:
Figured it out. Not the prettiest solution, but ayyy
class SchoolFilter(django_filters.FilterSet):
special_id = django_filters.CharFilter(field_name="special_id", method="special_id_filter", label="Special School ID")
def special_id_filter(self, queryset, name, value):
schools_pk = []
for obj in queryset:
if obj.special_id == value:
schools_pk.append(obj.pk)
queryset = queryset.filter(pk__in=schools_pk)
return queryset
class Meta:
model = School
fields = {
'name': ['icontains'],
'special_id': ['icontains'],
}
You can't. FilterSet will only filter on actual fields, since FilterSet alters a QuerySet.
QuerySets do a database call based on the filters applied, which means you can only filter on fields actually stored in the database.
You could annotate your QuerySet to add the special_id, but an annotation like this is pretty complex to chain together.
A better way to do this would be to create a custom filter on your FilterSet, but I'm not exactly sure how to do this. If you can explain what special_id is, and exactly why you want to search it through icontains, then I could maybe point you in the right direction.
This is an implementation of a MethodFilter, which I think is similar what you want.
FilterSet operates by filtering queryset (adding where conditions to the underlying sql). Which means, FilterSet can operate only on Columns that are present in the database. Here the special_id is a computed property (It is not a column, it is calculated on the fly using other fields/columns), So it wont work.
The work around is to make special_id a normal field/column, compute the value at runtime and write to database at the time of save.

Sort field values alphabetically using marshmallow

I need to sort the values for each field alphabetically when they get returned by the API. It seems marshmallow's pre_dump method is the way to pre-process the data before serializing, but I haven't been able to figure this out. I've read the docs multiple times and Googled but haven't found the answer.
class UserSettingsSchema(UserSchema):
class Meta:
fields = (
"id",
"injuries",
"channel_levels",
"movement_levels",
"equipments",
"goals",
"genres"
)
#pre_dump
def pre_dump_hook(): # what do I pass in here?
# alphabetize field values, eg, all the genres will be sorted
equipments = fields.Nested(EquipmentSchema, many=True)
goals = fields.Nested(GoalSchemaBrief, many=True)
genres = fields.Nested(GenreSchemaBrief, many=True)
The answer given by PoP does not address the fact that I needed the values sorted. This is how I did it:
#post_load
def post_load_hook(self, item):
item['genres'] = sorted(item['genres'])
return item
As mentioned in the docs:
By default, receives a single object at a time, regardless of whether many=True is passed to the Schema.
Here's an example:
from marshmallow import *
class MySchema(Schema):
name = fields.String()
#pre_dump
def pre_dump_hook(self, instance):
instance['name'] = 'Hello %s' % instance['name']
Now you can do:
schema = MySchema()
schema.dump({'name': 'Bill'})
>>> MarshalResult(data={'name': 'Hello Bill'}, errors={})

Django Rest Framework receive primary key value in POST and return model object as nested serializer

I'm not completely sure that the title of my question is as specific as I wanted it to be, but this is the case:
I have a HyperlinkedModelSerializer that looks like this:
class ParentArrivalSerializer(serializers.HyperlinkedModelSerializer):
carpool = SchoolBuildingCarpoolSerializer()
class Meta:
model = ParentArrival
As you can see the carpool is defined as a nested serializer object and what I want is to be able to make a POST request to create a ParentArrival in this way (data as application/json):
{
...
"carpool": "http://localhost:8000/api/school-building-carpools/10/"
...
}
And receive the data in this way:
{
"carpool": {
"url": "http://localhost:8000/api/school-building-carpools/10/"
"name": "Name of the carpool",
...
}
}
Basically, I'm looking for a way to deal with nested serializers without having to send data as an object (but id or url in this case) in POST request, but receiving the object as nested in the serialized response.
I have been happy with my previous solution, but decided to look again and I think I have another solution that does exactly what you want.
Basically, you need to create your own custom field, and just overwrite the to_representation method:
class CarpoolField(serializers.PrimaryKeyRelatedField):
def to_representation(self, value):
pk = super(CarpoolField, self).to_representation(value)
try:
item = ParentArrival.objects.get(pk=pk)
serializer = CarpoolSerializer(item)
return serializer.data
except ParentArrival.DoesNotExist:
return None
def get_choices(self, cutoff=None):
queryset = self.get_queryset()
if queryset is None:
return {}
return OrderedDict([(item.id, str(item)) for item in queryset])
class ParentArrivalSerializer(serializers.HyperlinkedModelSerializer):
carpool = CarpoolField(queryset=Carpool.objects.all())
class Meta:
model = ParentArrival
This will allow you to post with
{
"carpool": 10
}
and get:
{
"carpool": {
"url": "http://localhost:8000/api/school-building-carpools/10/"
"name": "Name of the carpool",
...
}
}
It's simple.
As you know, Django appends "_id" to the field name in the ModelClass, and you can achieve it in the SerializerClass, and the original filed can also be achieved. All you have to do is like this
class ParentArrivalSerializer(serializers.HyperlinkedModelSerializer):
# ...
carpool_id = serializers.IntegerField(write_only=True)
carpool = SchoolBuildingCarpoolSerializer(read_only=True)
# ...
class Meta:
fields = ('carpool_id', 'carpool', ...)
And use carpool_id in POST request.
How about overriding the to_representation method?
class YourSerializer(serializers.ModelSerializer):
class Meta:
model = ModelClass
fields = ["id", "foreignkey"]
def to_representation(self, instance):
data = super(YourSerializer, self).to_representation(instance)
data['foreignkey'] = YourNestedSerializer(instance.foreignkey).data
return data
One way to do it is to keep 'carpool' as the default you get from DRF, and then add a read-only field for the nested object.
Something like this (I don't have time to test the code, so consider this pseudo-code. If you cannot get it to work, let me know, and will spend more time):
class ParentArrivalSerializer(serializers.HyperlinkedModelSerializer):
carpool_info = serializers.SerializerMethodField(read_only=True)
class Meta:
model = ParentArrival
fields = ('id', 'carpool', 'carpool_info',)
def get_carpool_info(self, obj):
carpool = obj.carpool
serializer = SchoolBuildingCarpoolSerializer(carpool)
return serializer.data
If your only nested object is carpool, I would also suggest switching to the regular ModelSerializer so carpool only shows the ID (10) and the nested object then can show the URL.
class ParentArrivalSerializer(serializers.ModelSerializer):
....
and then if it all works, you will be able to do a post with
{
"carpool": 10
}
and your get:
{
"carpool": 10
"carpool_info": {
"url": "http://localhost:8000/api/school-building-carpools/10/"
"name": "Name of the carpool",
...
}
}
I have never found another solution, so this is the trick I have used several times.

How can one customize Django Rest Framework serializers output?

I have a Django model that is like this:
class WindowsMacAddress(models.Model):
address = models.TextField(unique=True)
mapping = models.ForeignKey('imaging.WindowsMapping', related_name='macAddresses')
And two serializers, defined as:
class WindowsFlatMacAddressSerializer(serializers.Serializer):
address = serializers.Field()
class WindowsCompleteMappingSerializer(serializers.Serializer):
id = serializers.Field()
macAddresses = WindowsFlatMacAddressSerializer(many=True)
clientId = serializers.Field()
When accessing the serializer over a view, I get the following output:
[
{
"id": 1,
"macAddresses": [
{
"address": "aa:aa:aa:aa:aa:aa"
},
{
"address": "bb:bb:bb:bb:bb:bb"
}
],
"clientId": null
}
]
Almost good, except that I'd prefer to have:
[
{
"id": 1,
"macAddresses": [
"aa:aa:aa:aa:aa:aa",
"bb:bb:bb:bb:bb:bb"
],
"clientId": null
}
]
How can I achieve that ?
Create a custom serializer field and implement to_native so that it returns the list you want.
If you use the source="*" technique then something like this might work:
class CustomField(Field):
def to_native(self, obj):
return obj.macAddresses.all()
I hope that helps.
Update for djangorestframework>=3.9.1
According to documentation, now you need override either one or both of the to_representation() and to_internal_value() methods. Example
class CustomField(Field):
def to_representation(self, value)
return {'id': value.id, 'name': value.name}
Carlton's answer will work do the job just fine. There's also a couple of other approaches you could take.
You can also use SlugRelatedField, which represents the relationship, using a given field on the target.
So for example...
class WindowsCompleteMappingSerializer(serializers.Serializer):
id = serializers.Field()
macAddresses = serializers.SlugRelatedField(slug_field='address', many=True, read_only=True)
clientId = serializers.Field()
Alternatively, if the __str__ of the WindowsMacAddress simply displays the address, then you could simply use RelatedField, which is a basic read-only field that will give you a simple string representation of the relationship target.
# models.py
class WindowsMacAddress(models.Model):
address = models.TextField(unique=True)
mapping = models.ForeignKey('imaging.WindowsMapping', related_name='macAddresses')
def __str__(self):
return self.address
# serializers.py
class WindowsCompleteMappingSerializer(serializers.Serializer):
id = serializers.Field()
macAddresses = serializers.RelatedField(many=True)
clientId = serializers.Field()
Take a look through the documentation on serializer fields to get a better idea of the various ways you can represent relationships in your API.

Categories

Resources