Django get_FOO_Display() for range list choices - django

STATUS_APPROVE, STATUS_REJECT, STATUS_PROC, STATUS_PEND, STATUS_PAID = range(5)
STATUS_CHOICES = (
(STATUS_APPROVE, 'Approved'),
(STATUS_REJECT, 'Rejected'),
(STATUS_PROC, 'Processing'),
(STATUS_PEND, 'Pending'),
(STATUS_PAID, 'Paid'),
)
class ExpenseClaim(TimeStampedModel):
status = models.PositiveIntegerField(default=STATUS_PEND,
choices=STATUS_CHOICES, blank=False)
class MileageClaimSerializer(serializers.ModelSerializer):
status = serializers.ChoiceField(choices=STATUS_CHOICES)
I cannot work out a way to get foo display for a choice list with range. So the range keys are used to store the point in list. I need the serializer to return the word and not the key.

if this serializer is readonly you can use the source attribute inside a CharField
status = serializers.CharField(source='get_status_display')
https://www.django-rest-framework.org/api-guide/fields/#source

Related

django bulk_create with Null value

models.py
class control(models.Model):
amount = models.IntegerField()
driver = models.ForeignKey(driver, on_delete=models.CASCADE, null=True, blank=True)
views.py
controlValues = [
(1,1,1),
(2,8,None)
]
control.objects.bulk_create([
control(
id = i[0],
amount = i[1],
driver = driver(id = i[2])
) for i in controlValues], ignore_conflicts=True
)
I got error:
bulk_create() prohibited to prevent data loss due to unsaved related object 'driver'.
How can I set Null for driver? I'm using mysql.
If the value is None, you should not construct a driver model with that object, but thus work with:
control.objects.bulk_create([
control(
id=id,
amount=amount,
driver=driver(id=dr_id) if dr_id is not None else None
) for id, amount, dr_id in controlValues],
ignore_conflicts=True
)
or a lot simpler:
control.objects.bulk_create([
control(
id=id,
amount=amount,
driver_id=dr_id
) for id, amount, dr_id in controlValues],
ignore_conflicts=True
)
Note: Models in Django are written in PascalCase, not snake_case,
so you might want to rename the model from driver to Driver.

The best way to do an efficient filter query in django

models.py file
I am not so good at this aspect in Django. Please can someone help me? I wish to know if there is a more efficient way for the class method already_voted
class Vote(TimeStamped):
voter = models.ForeignKey(get_user_model(), verbose_name=_("Vote"), on_delete=models.CASCADE)
contester = models.ForeignKey(Contester, verbose_name=_("Contester"), on_delete=models.CASCADE,
help_text=("The chosen contester"), related_name="votes")
ip_address = models.GenericIPAddressField(
_("Voter's IP"),
protocol="both",
unpack_ipv4=False,
default="None",
unique=True
)
num_vote = models.PositiveIntegerField(_("Vote"), default=0)
class Meta:
unique_together = ('voter','contester')
verbose_name = _("Vote")
verbose_name_plural = _("Votes")
permissions = (
("vote_multiple_times", "can vote multiple times"),
)
....
....
#classmethod
def already_voted(cls, contester_id, voter_id=None, ip_addr=None):
return cls.objects.filter(contester_id=contester_id).exists() and \
(cls.objects.filter(ip_address=ip_addr).exists() or \
cls.objects.filter(voter_id=voter_id).exists())
The class method may be right, but your model needs one more index:
contester = models.ForeignKey( db_index= True #... )
Notice that:
voter doesn't need index because is on first place on unique_together constraint.
contester needs index because, despite it is on unique_together, doesn't is place on first position of the constraint.
ip_address doesn't need index because has unique constraint.
Also:
unique_together is deprecated and should be a list of tuples (not just a tuple)
Edited
Edited 5 feb 2021 due to OP comment
You can get results in just one hit using Exists but it is less readable, also, I'm not sure if it is more efficient or the best way:
from django.db.models import Exists
q_ip=Vote.objects.filter(ip_address="1")
q_voter=Vote.objects.filter(voter=2)
already_voted=(
Vote
.objects
.filter(contester=3)
.filter(Exists(q_ip)|Exists(q_voter))
.exists())
The underlying sql, you can see this is just one query:
SELECT ( 1 ) AS "a"
FROM "a1_vote"
WHERE ( "a1_vote"."contester" = 3
AND ( EXISTS(SELECT U0."id",
U0."voter",
U0."contester",
U0."ip_address",
U0."num_vote"
FROM "a1_vote" U0
WHERE U0."ip_address" = '1')
OR EXISTS(SELECT U0."id",
U0."voter",
U0."contester",
U0."ip_address",
U0."num_vote"
FROM "a1_vote" U0
WHERE U0."voter" = 2) ) )
LIMIT 1

Filtering X most recent entries in each category of queryset

Question is regarding filtering X most recent entries in each category of queryset.
Goal is like this:
I have a incoming queryset based on the following model.
class UserStatusChoices(models.TextChoices):
CREATOR = 'CREATOR'
SLAVE = 'SLAVE'
MASTER = 'MASTER'
FRIEND = 'FRIEND'
ADMIN = 'ADMIN'
LEGACY = 'LEGACY'
class OperationTypeChoices(models.TextChoices):
CREATE = 'CREATE'
UPDATE = 'UPDATE'
DELETE = 'DELETE'
class EntriesChangeLog(models.Model):
content_type = models.ForeignKey(
ContentType,
on_delete=models.CASCADE,
)
object_id = models.PositiveIntegerField(
)
content_object = GenericForeignKey(
'content_type',
'object_id',
)
user = models.ForeignKey(
get_user_model(),
verbose_name='user',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='access_logs',
)
access_time = models.DateTimeField(
verbose_name='access_time',
auto_now_add=True,
)
as_who = models.CharField(
verbose_name='Status of the accessed user.',
choices=UserStatusChoices.choices,
max_length=7,
)
operation_type = models.CharField(
verbose_name='Type of the access operation.',
choices=OperationTypeChoices.choices,
max_length=6,
)
And I need to filter this incoming queryset in a such way to keep only 4 most recent objects (defined by access_time field) in each category. Categories are defined by ‘content_type_id’ field and there are 3 possible options.
Lets call it ‘option1’, ‘option2’ and ‘option3’
This incoming queryset might contain different amount of objects of 1,2 or all 3 categories. This is can’t be predicted beforehand.
DISTINCT is not possible to use as after filtering operation this queryset might be ordered.
I managed to get 1 most recent object in a following way:
# get one most recent operation in each category
last_operation_time = Subquery(
EntriesChangeLog.objects.filter(user=OuterRef('user')).values('content_type_id').
annotate(last_access_time=Max(‘access_time’)).values_list('last_access_time', flat=True)
)
queryset.filter(access_time__in=last_operation_time)
But I have a hard time to figure out how to get last 4 most recent objects instead of last one.
This is needed for Django-Filter and need to be done in one query.
DB-Postgres 12
Do you have any ideas how to do such filtration?
Thanks...
pk_to_rank = queryset.annotate(rank=Window(
expression=DenseRank(),
partition_by=('content_type_id',),
order_by=F('access_time').desc(),
)).values_list('pk', 'rank', named=True)
pks_list = sorted(log.pk for log in pk_to_rank if log.rank <= value)
return queryset.filter(pk__in=pks_list)
Managed to do it only this way by spliting queryset in 2 parts. Option with 3 unions is also possible but what if we have 800 options instead 3 - make 800 unions()??? ges not...

Django selecting a count

I'm fairly new to Django and I want to get the total clients a user has (my users will be selling something through my website) so I have a table called orders where I keep the user_id of the user who purchased and the product_id being purchased. Each product id is related to a product that has a user (the one I'm doing the query for):
select COUNT(distinct(o.user_id)) as total_clients from `order` o
inner join product p on p.id=o.product_id where p.user_id=32;
User id 32 is logged in and I want to show him how many clients purchased his products.
I want to do this in a get instead of filter as it makes more sense.
Here's what my logic tells me to write:
clients = Order.objects.get(
status = Order.COMPLETED,
product__user = self.user
).annotate(
total_clients = Count( 'user', distinct = True )
)
return clients.total_clients
.and here's what it returns:
Exception Type: MultipleObjectsReturned
Exception Value: get() returned more than one Order -- it returned 2!
I could probably be running a query instead of using the orm but I don't want that. This is a rather simple query that I'm sure Django is handling very easily and I want to keep away from writing strings in my code.
Here's my model setup:
class UserProfile( models.Model ):
user = models.OneToOneField( User, related_name = 'profile' )
....
def get_total_clients( self ):
clients = Order.objects.get(
status = Order.COMPLETED,
product__user = self.user
).annotate(
total_clients = Count( 'user', distinct = True )
)
return clients.total_clients
class Order( models.Model ):
PENDING = 0
COMPLETED = 1
REFUNDED = 2
STATUS_CHOICES = (
(PENDING, "Pending"),
(COMPLETED, "Completed"),
(REFUNDED, "Refunded"),
)
user = models.ForeignKey( User, related_name = "orders" )
product = models.ForeignKey( Product, related_name = "orders" )
amount = models.DecimalField( max_digits = 6, decimal_places = 2, default = 0.00 )
status = models.SmallIntegerField(
choices = STATUS_CHOICES, default = PENDING, db_index = True
)
class Product( models.Model ):
user = models.ForeignKey( User, related_name = "unlocks" )
name = models.CharField( max_length = 255 )
Django queryset have a count method:
clients = Order.objects.filter(
status = Order.COMPLETED,
product__user = self.user
)
return clients.count()
If i got you right, you are intrested in the amount of consumers who ordered a product from a user. Some docs, that may be helpful.
My suggestion is:
result = Product.objects.distinct().filter(
# or any query:
orders__status=Order.COMPLETED,
user=default_user
).aggregate( # see the difference between aggregate() and annotate()
total_users=Count('orders__user', distinct=True)
)
I expect the result to be: {'total_users': NUM_OF_USERS}
In raw SQL it will be something like:
SELECT DISTINCT COUNT(DISTINCT "main_order"."user_id") AS
"total_users" FROM "main_product" INNER JOIN "main_order" ON (
"main_product"."id" = "main_order"."product_id" ) WHERE
("main_order"."status" = STATUS AND "main_product"."user_id" = USER_ID)
Is that what you wanted?

cannot filter by an added field in django

I want to filter by "status" which is an added field but it does not work:
serial_number_list = TestSerialNumber.objects.filter(test_pool = test_pool, status='rejected')
I get this error:
Cannot resolve keyword 'status' into field. Choices are: id, serial_number, test_pool, testresult, testrun
This is my model class:
class TestSerialNumber(models.Model):
serial_number = models.ForeignKey("core.SerialNumber", on_delete=models.PROTECT)
test_pool = models.ForeignKey("TestPool", blank=True, null=True)
def __unicode__(self):
return self.serial_number.serial_number
def panel_code(self):
return self.serial_number.panel.panel_code
def status(self):
try:
test_result = self.testresult_set.latest('report')
except TestResult.DoesNotExist:
return 'unknown'
else:
return test_result.test_status.name
Also, when exporting to json, i get just the fields: serial_number and test_pool, just the ids and not the rest.
Thanks in advance!
If you want to do it you have to create a field with name "status"
STATUS_CHOICES = (
(1, 'rejected'),
(2, 'blabla'),
)
status = models.IntegerField(choices=STATUS_CHOICES)
By the structure of your code that you have provided, status is not a field its a method. So if you want to get the status for and object you need to filter it out first and then you can access the method that will return its status for that object.
serial_number_list = TestSerialNumber.objects.filter(test_pool = test_pool)
print serial_number_list[0].status # as filter return a list
this will give you the status for that object.
The error Cannot resolve keyword 'status' into field. Choices are:id, serial_number, test_pool, testresult, testrun occurs when you try to access the model with a invalid field, by invalid I mean the field that doesnot exist in the model or the database