How to add a calculated field to a django query expression - django-queryset

I have a Django model, DocumentComments, with two datetime fields, created and updated. I am working on a search function that parses a search string and returns a Q expression to query the DocumentComments model based on the values in the search string.
I need to write something like Q(created.year=xxxx), where created.year is the year in the created datetime field. But "keywords can't be expressions" as Django has been telling me all morning.
I tried using a custom model manager and annotating the default queryset with a year field, but that did not work as I can't seem to access the created.year value in the get_queryset function.
class DocumentCommentManager(models.Manager):
def get_queryset(self):
c_year = self.created.year
u_year = self.updated.year
return super(DocumentCommentManager, self).get_queryset().annotate(created_year=c_year, updated_year=u_year)
What am I missing, or what is a better way to accomplish my goal?
Thanks!
Mark

I was able to solve my problem using Django's db function Extract (https://docs.djangoproject.com/en/3.1/ref/models/database-functions/#extract)
My DocumentCommentManager:
from django.db.models.functions import Extract
class DocumentCommentManager(models.Manager):
def get_queryset(self):
return super(DocumentCommentManager, self).get_queryset().annotate(created_year=Extract("created","year"))
This solves my original problem of adding a calculated datetime field to the model queries.
I still have not found a general way to add a calculated field to a model query using Q expressions. If you can share any examples, that would be great!

Related

Django REST Framework - access verbose_name of fields in ModelSerializer

Say I have the following Model:
class Book(Model):
title = CharField(verbose_name="Book title")
and a ModelSerializer:
class BookSerializer(ModelSerializer):
class Meta:
model = Book
fields = "__all__"
I would like to have a function get_verbose_names which returns verbose names of the fields in the model. This is what I have so far:
def get_verbose_names(serializer):
return [field.label for field in serializer.get_fields().values()]
It seems to work fine but problems occur when I use this for the builtin User model. The only fields which work are ID, E-mail, Active, Superuser status and Staff status. The special thing about those fields is that their verbose name differs from their name. Django REST Framework is probably hiding a super-smart logic which checks this and refuses to set the field label to its verbose name in such cases.
Do Django REST Framework's fields have the verbose names hidden somewhere, or they don't copy them from the original Django model fields at all and I am screwed? Or will the trick be to override this logic? I tried and could not find it.
Django REST Framework really has the mentioned "super-smart logic". It is the function needs_label in utils.field_mapping:
def needs_label(model_field, field_name):
"""
Returns `True` if the label based on the model's verbose name
is not equal to the default label it would have based on it's field name.
"""
default_label = field_name.replace('_', ' ').capitalize()
return capfirst(model_field.verbose_name) != default_label
Probably the easiest way to bypass this annoying feature is to do this:
def get_verbose_names(serializer):
return [field.label or name.replace("_", " ").capitalize()
for name, field in serializer.get_fields().items()]
Explained in words, check the field label and if none was auto-generated for it, use the needs_label logic to determine it.

django-filter (django rest framework) - method filter with 3 parameters

I'm using django rest framework to create an api and django-filter to provide nice way for users to see how filters work in the browsable api part of the site.
I have a need to filter queryset by a result of a method call. Unfortunately it needs 3 parameters to be provided by the user (calculate distance from centre point using lat, lng, radius).
I know I can declare a non model field in the filterset with a method to call but then just one parameter is passed to the method.
I can declare 3 non model fields but then I end with 3 different methods or calling the same one with 1 changing parameter 3 times.
example code:
class PersonFilter(FilterSet):
status = ChoiceFilter(field_name='status', choices=Person.STATUS_CHOICES)
# I show an example of what I need to achieve below, obviously it will not work as
# I need to give the user 3 fields to fill in and call the method only once with their values...
latitude = NumberFilter(label='latitude', method='check_if_in_range')
longitude = NumberFilter(label='longitude', method='check_if_in_range')
radius = NumberFilter(label='radius', method='check_if_in_range')
class Meta:
model = Person
fields = 'status', 'latitude', 'longitude', 'radius'
example method to filter by 3 parameters:
def check_if_in_range(self, queryset, name, value):
here I need access to the values from 3 non model form fields...
do calculation and filter the queryset
return <filtered queryset>
Is this even doable?
I want my users to be able to use:
<base_url>?longitude=234234&latitude=232342&radius=34
to filter persons through the API...
Thank you for your time & help!
Tomasz
You can do something like this:
class PersonFilter(FilterSet):
status = ChoiceFilter(field_name='status', choices=Person.STATUS_CHOICES)
radius = NumberFilter(label='radius', method='check_if_within_range')
class Meta:
model = Person
fields = 'status', 'radius'
def check_if_within_range(self, queryset, name, value):
base_point = Point(latitude=float(self.request.GET.get("latitude")),
longitude=float(self.request.GET.get("longitude")))
return queryset.distance_within(base_point, value)
Based on how you want to calculate the distance and filter queryset you need to have a custom method. Here I have assumed you'll have distance_within() method in your custom queryset manager.
You can refactor as per your need/structure.

How to serialize tuples in django rest framework? Or, is there any way to convert tuple into queryset?

My views.py contains a view like:
class RankViewSet(generics.mixins.CreateModelMixin,
generics.mixins.ListModelMixin,
viewsets.GenericViewSet):
def get_queryset(self):
with connection.cursor() as cursor:
query = "SELECT ... "
cursor.execute(query)
row = cursor.fetchall()
return row
Here, get_queryset(self) returns some tuples like following:
((name1, college1, semester1), (name2, college2, semester2), (name3, college3, semester3), ...)
All of the fields are from different models, so I have to fetch these data using raw sql. How can I serialize this tuple in django rest framework? Or Is there any way to convert tuple in queryset?
Only just seen this question but answer may still be useful...
Off the top of my head you have a few options:
serializers.ListField -as you basically have a list of lists, if the content is all the same type you can do child=serializers.ListField(child=serializers.CharField())
serializers.SerializerMethodField()..and call mySerialiser(many=True) with the set
name = serializers.SerializerMethodField()
def get_name(self, obj):
return obj[0]
Having said this unless you have a very specific reason to use a raw sql query, as you are using django you should take advantage of its ORM engine and then you can use rest_framework model serializer. If speed is an issue you can use .only() and .defer() with your queries.

Django-nonrel sorting by foreignkey

Is there a way of returning items from a database in django-nonrel, using 'order_by' on a foreignkey?
Full details are as follows:
#Models.py
class Post(models.Model):
article = models.TextField(help_text='Paste or type HTML in here')
pub_date = models.DateField()
....
class TagItems(models.Model):
title = models.CharField(max_length=200)
....
class TagRel(models.Model):
the_post = models.ForeignKey('Post')
the_tag = models.ForeignKey('Tag')
TagRel defines a ManytoMany relationship between Post and TagItems classes.
I am wanting to get a list of articles for each tag.
#Desire output
My tag
-my first post
-my second post
My second tag
- my other post
- another post
All is good so far, as I use the following to filter the data:
def tagged_posts():
tag_items = TagItems.objects.all()
li =[]
for item in tag_items:
tag_rel_item = TagRel.objects.filter(the_tag__pk = item.pk)
li.append(tag_rel_item)
return {'list_of_objects': li}
I am using db-indexer to define the filter part of the query in db-indexes.py. All this works fine but I want to order my posts by publication dates.
Django docs tell me to use:
TagRel.objects.filter(the_tag__pk = item.pk).order_by('the_tag__pub_date')
But the order_by('the_tag__pub_date') part does not appear to be supported by django-nonrel.
The following also works in normal Django:
TagRel.objects.filter(the_tag__pk = item.pk).order_by('the_post')
This works because the Posts are already sorted by date in the model.
But this also does not appear to work in django-nonrel.
So my question is how do I return my posts ordered by date (latest>oldest)?
Thanks in advance
I'm taking a guess at this - you're using a ManyToManyField. I believe that's implemented using a ListProperty on App Engine's datastore.
See the section in the datastore documentation labeled "Properties With Multiple Values Can Have Surprising Behaviors":
http://code.google.com/appengine/docs/python/datastore/queries.html
That's most likely why your results appear unsorted. ManyToMany relations aren't supported natively in GAE. You'd probably have to sort them yourself after you get the results back.

Grails: can remoteField update multiple fields?

Assume i have a book entity with an isbn field.
When entered a isbn number, i want 2 fields to be updated: title and author.
My controller looks like this:
def ajaxGetBook = {
def book = Book.findByIsbn(params.isbn)
if(book==null) book = new Book()
render book as JSON
}
So my call works, and i get a full JSON Book.
Now i would like to update 2 texfields by the update attribute
<g:remoteField action="ajaxGetBook" update="title" name="isbn" value="${bookInstance?.book?.isbn}" paramName="isbn"/>
Now the title field gets updated with the full book object, so that doesn't work.
Is it possible to update field title with only the JSON book.title?
Is it possible to update more fields at once?
I could render book.title as JSON but that works for only one field.
Thank you
Well, the g:remoteField tag is explicitly for one field, so you can't use it to update more than one. So, you're left with 2 easy choices:
Use 2 g:remoteField calls... this isn't a bad option, as they would happen in near parallel, as they are Async calls.
Roll your own Ajax. use g:remoteFunction instead, and have the JS function that you place in the "success" attribute take your book, and update the appropriate HTML fields.

Resources